Введение

В статье Пишем анонимный мессенджер с нуля было реализовано два приложения - HLS и HLM. Первое приложение представляло собой ядро анонимной сети, которое переводило обычный трафик в анонимизированный и наоборот. Второе приложение коннектилось к HLS и представляло собой в первую очередь прикладные функции типа отправления/принятия сообщений, сохранение сообщений в БД, графический интерфейс.

Композиция HLS+HLM представляет собой реализацию анонимного мессенджера, построенного полностью на децентрализованной одноранговой (peer-to-peer) архитектуре. Со стороны отказоустойчивости, и в том числе со стороны анонимности, данное свойство является положительным, тем не менее, существует и ряд недостатков. К одним из таких можно отнести сохранение сообщений, когда получатель находится в offline.

Peer-to-peer сети

Существует несколько видов одноранговых сетей, а именно:

  1. Централизованная одноранговая архитектура. Представляет собой существование двух ролей - клиентов и ретрансляторов. Клиенты в такой системе генерируют и принимают всю информацию. Ретрансляторы служат исключительно для перенаправления клиентской информации, не содержа в себе никакой дополнительной логики.

  2. Децентрализованная одноранговая архитектура. Представляет собой "сращивание" воедино клиентов и ретрансляторов, в следствие чего каждый клиент теперь становится способным осуществлять перенаправления поступаемой ему клиентской информации извне. Соединённая сущность в виде клиента и ретранслятора именуется узлом.

  3. Распределённая одноранговая архитектура. Подвид децентрализованной одноранговой архитектуры. Зародилась в следствии "коррозии" децентрализованных форм централизованными. Иными словами, в децентрализованных архитектурах существует проблема, когда узлы начинают выбирать малое количество стабильных узлов для последующей ретрансляции, тем самым приводя систему к неявному виду централизации. Распределённая одноранговая архитектура переводит качество соединений на их количество.

Со стороны обычных децентрализованных клиент-безопасных приложений это не является проблемой, потому как некоторые узлы могут выдвигать себя как промежуточное хранилище между отправителем и получателем, становясь определённым видом ретрансляторов. Т.к. таковые приложения являются клиент-безопасными, то вся безопасность системы держится исключительно или в большей мере на клиентской стороне. Серверная (или ретранслирующая) сторона становится неспособной что-либо скомпромитировать.

Но так или иначе, со стороны анонимизирующих клиент-безопасных приложений проблема становится более значима. В отличие от обычных клиент-безопасных приложений, где сохраняются исключительно истинные сообщения, в анонимных клиент-безопасных приложениях становится необходимым сохранять совершенно весь трафик сети на определённом периоде времени. Связано это с тем, что любая анонимная сеть остаётся анонимной до тех пор пока сохраняется фактор запутывающей маршрутизации. Таким образом, если клиент-безопасное приложение основано на сети Tor и хочет оставаться анонимным даже с учётом сохранения сообщений, то оно должно в ровно такой же степени воспроизвести ранее использованный трафик.

Запутывающая маршрутизация

Маршрутизация в анонимных сетях не является примитивной и ставит эффективность распространения объектов опциональным параметром (низкие / высокие задержки), потому как главной целью становится создание запутывающего алгоритма (анонимизатора), который приводил бы к трудоёмкости анализа истинного пути от точки отправления до точки назначения. Производительность, эффективность «чистой» маршрутизации теряется, заменяясь особенностью алгоритма. В таких условиях, сами скрытые сети становятся медленными и сложными в применении (в том числе и с низкими задержками), что также частично или полноценно отодвигает их прикладное и повседневное использование в настоящее время.

Назовём приложение, сохраняющее трафик, как HLT - Hidden Lake Traffic. На основе этого будет образована композиция вида HLS+HLM+HLT.

Сохранение трафика сети

Задача HLT крайне примитивна - эмулировать себя как HLS для поточной выгрузки трафика сети и по запросу любых прикладный приложений, подобия HLM, выдавать таковой трафик в обход HLS. В такой концепции, HLT может быть самостоятельным и отделённым приложением от HLS, иными словами HLT может находиться у одного пользователя и коннектиться к HLS другого участника. Ровно также, HLM на стороне одного пользователя, может подгружать сообщения от HLT на стороне другого участника. Все приведённые концепции являются безопасными, потому как каждое приложение исполняет исключительно свои узкоспециализированные функции, не возлагая ответственность действий на другие приложения, за исключением некоторых сценариев как компромиссов, о которых мы поговорим позже.

Реализация HLT

Первое, что необходимо сделать - это уметь подгружать трафик с HLS. Для этого становится необходимым уметь эмулировать HLT как HLS, но исключительно для чтения сообщений. HLT не нужны функции генерации какого-либо трафика. Для того, чтобы сделать лишь некоторые функции от HLS, не добавляя излишних, необходимо спуститься на более низкоуровневую реализацию HLS - до сетевых коммуникаций.

HLT должен пытаться постоянно поддерживать соединение с HLS, даже при условиях, когда последний часто выходит из строя и отключается от сети.

func initConnKeeper(cfg config.IConfig, db database.IKeyValueDB, logger logger.ILogger) conn_keeper.IConnKeeper {
	anonLogger := anon_logger.NewLogger(hlt_settings.CServiceName)
    // ConnKeeper пытается всегда связаться с указанным списком соединений, 
    // если к таковым он ещё не подключен.
	return conn_keeper.NewConnKeeper(
		conn_keeper.NewSettings(&conn_keeper.SSettings{
			FConnections: func() []string { return []string{cfg.Connection()} },
			FDuration:    hlt_settings.CNetworkWaitTime,
		}),
        // Node должна обладать ровно такими же полями, как и Node в HLS за
        // исключением FMaxConnects, которое со стороны HLT всегда константно.
		network.NewNode(
			network.NewSettings(&network.SSettings{
				FMaxConnects: 1, // one HLS from cfg.Connection()
				FConnSettings: conn.NewSettings(&conn.SSettings{
					FNetworkKey:  cfg.Network(),
					FMessageSize: db.Settings().GetWorkSize(),
					FTimeWait:    hlt_settings.CNetworkWaitTime,
				}),
			}),
		).Handle(
            // В отличие от HLS, которое принимает сообщение, 
            // пытается расшифровать его, проверяет отправителя 
            // со списком друзей и перераспределяет его по другим сервисам, 
            // подобия HLM, в HLT необходимо лишь сохранять полученное 
            // сообщение в БД без каких-либо попыток расшифрования.
			hlt_settings.CNetworkMask,
			func(_ network.INode, conn conn.IConn, reqBytes []byte) {
				msg := message.LoadMessage(
					reqBytes,
					message.NewParams(
						db.Settings().GetMessageSize(),
						db.Settings().GetWorkSize(),
					),
				)
				if msg == nil {
					logger.Warn(anonLogger.FmtLog(anon_logger.CLogWarnMessageNull, nil, 0, nil, conn))
					return
				}

				var (
					hash  = msg.Body().Hash()
					proof = msg.Body().Proof()
				)

				strHash := encoding.HexEncode(hash)
				if _, err := db.Load(strHash); err == nil {
					logger.Info(anonLogger.FmtLog(anon_logger.CLogInfoExist, hash, proof, nil, conn))
					return
				}

				if err := db.Push(msg); err != nil {
					logger.Erro(anonLogger.FmtLog(anon_logger.CLogErroDatabaseSet, hash, proof, nil, conn))
					return
				}

				logger.Info(anonLogger.FmtLog(anon_logger.CLogInfoUnencryptable, hash, proof, nil, conn))
			},
		),
	)
}

База данных в HLT является структурой "кольцо". Иными словами, HLT выставляет лимит хранимых сообщений и если таковой превышается, то новые сообщения начинают перезаписывать старые.

Второе, что необходимо сделать, это реализовать API для выдачи сохранённого трафика сторонним сервисам. Для этого необходимо написать три основных действия - Hashes, Load, Push. Первая функция выдаёт список всех хешей сохранённых сообщений в БД. Вторая функция скачивает сообщение по хешу. Третья функция загружает сообщение на HLT. Последняя функция может быть использована сторонними сервисами в отрыве от HLS. О такой возможности мы поговорим в разделе "Использование HLT в отрыве от HLS".

func initServiceHTTP(cfg config.IConfig, db database.IKeyValueDB) *http.Server {
	mux := http.NewServeMux()

	mux.HandleFunc(pkg_settings.CHandleIndexPath, handler.HandleIndexAPI())
	mux.HandleFunc(pkg_settings.CHandleHashesPath, handler.HandleHashesAPI(db))
	mux.HandleFunc(pkg_settings.CHandleMessagePath, handler.HandleMessageAPI(db))

	return &http.Server{
		Addr:    cfg.Address(),
		Handler: mux,
	}
}

В коде HandleHashesAPI обладает исключительно функцией выдачи списка хешей GET-запросом. HandleMessageAPI внутри себя содержит две функции - Load (GET-запрос) и Push (POST-запрос).

Реализации HandleHashesAPI и HandleMessageAPI

HandleHashesAPI

package handler

import (
	"net/http"
	"strings"

	"github.com/number571/go-peer/cmd/hidden_lake/traffic/internal/database"
	pkg_settings "github.com/number571/go-peer/cmd/hidden_lake/traffic/pkg/settings"
	"github.com/number571/go-peer/internal/api"
)

func HandleHashesAPI(db database.IKeyValueDB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet {
			api.Response(w, pkg_settings.CErrorMethod, "failed: incorrect method")
			return
		}

		hashes, err := db.Hashes()
		if err != nil {
			api.Response(w, pkg_settings.CErrorLoad, "failed: load size")
			return
		}

		api.Response(w, pkg_settings.CErrorNone, strings.Join(hashes, ";"))
	}
}

HandleMessageAPI

func HandleMessageAPI(db database.IKeyValueDB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet && r.Method != http.MethodPost {
			api.Response(w, pkg_settings.CErrorMethod, "failed: incorrect method")
			return
		}

		switch r.Method {
		case http.MethodGet:
			query := r.URL.Query()
			msg, err := db.Load(query.Get("hash"))
			if err != nil {
				api.Response(w, pkg_settings.CErrorLoad, "failed: load message")
				return
			}

			api.Response(w, pkg_settings.CErrorNone, encoding.HexEncode(msg.Bytes()))
			return
		case http.MethodPost:
			var vRequest pkg_settings.SPushRequest

			err := json.NewDecoder(r.Body).Decode(&vRequest)
			if err != nil {
				api.Response(w, pkg_settings.CErrorDecode, "failed: decode request")
				return
			}

			if uint64(len(vRequest.FMessage)/2) > db.Settings().GetMessageSize() {
				api.Response(w, pkg_settings.CErrorPackSize, "failed: incorrect package size")
				return
			}

			msg := message.LoadMessage(
				encoding.HexDecode(vRequest.FMessage),
				message.NewParams(
					db.Settings().GetMessageSize(),
					db.Settings().GetWorkSize(),
				),
			)
			if msg == nil {
				api.Response(w, pkg_settings.CErrorMessage, "failed: decode message")
				return
			}

			err = db.Push(msg)
			if err != nil {
				api.Response(w, pkg_settings.CErrorPush, "failed: push message")
				return
			}

			api.Response(w, pkg_settings.CErrorNone, "success")
			return
		}
	}
}

HandleIndexAPI является исключительно функцией Ping'a и выглядит следующим образом.

func HandleIndexAPI() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		api.Response(w, hlt_settings.CErrorNone, hlt_settings.CTitlePattern)
	}
}

Это весь HLT как таковой. Примитивный и за счёт этого предсказуемый и, как следствие, безопасный. Тем не менее, текущая реализация HLT приводит к определённым компромиссам, где таковой перекладывает ответственность расшифрования и анализа подгружаемого сообщения на сторону HLM (или подобных прикладных приложений).

HLT можно было бы реализовать иначе, но это приводило бы 1) либо к усложнению HLS, что крайне нежелательно, потому как HLS - это основа всех HL систем и увеличение кода потенциально может породить больше багов, недостатков, сложности анализа; 2) либо к усложнению HLT, что будет приводить уже к неявным функциям расшифрования, дополнительным проверкам и т.п., что просто бы приводило к переносу ответственности, как и при текущей реализации. Можно было бы поступить и так, чтобы HLT перенаправлял весь сохранённый трафик вновь на HLS, но тогда это добавило бы функции отправления в реализацию HLT, который должен был бы постоянно соединяться со множеством сервисов HLS. Также это потенциально могло бы засорять очереди в HLS, т.к. могло бы накинуть большое количество трафика в одно мгновение.

Таким образом, было принято решение переложить всю ответственность обработки информации на вызывающую сторону и не пытаться как-либо перераспределять данный трафик или пытаться его расшифровывать самостоятельно на стороне HLT.

Использование HLT в отрыве от HLS

Интересной особенностью HLT, в нынешней его реализации, становится его возможное абстрагирование от HLS. Связано это с тем, что HLT не беспокоит с кем он связывается и от кого поступают сообщения, главное чтобы они были в нужном формате HLS, не более. Из этого следует, что HLT можно использовать как хранилище зашифрованных сообщений для каких-либо других приложений, в том числе и клиент-безопасных и не анонимных.

Это легко можно представить на следующем примере. Создадим для начала клиента, который может отправлять какие-либо сообщения (конечно же в зашифрованном виде), получать сообщения (конечно же пытаясь расшифровывать их) и получать список всех сообщений на HLT.

const (
	pldHead = 0x1
)

func main() {
    // Подключаемся к HLT. Т.к. HLT использует те же конфигурации, что и HLS,
    // то можно взять за основу константы из HLS напрямую.
	hltClient := hlt_client.NewClient(
		hlt_client.NewBuilder(),
		hlt_client.NewRequester(
			"http://localhost:9573",
			message.NewParams(
				hls_settings.CMessageSize,
				hls_settings.CWorkSize,
			),
		),
	)

    // Читаем приватный ключ из файла.
	readPrivKey, err := filesystem.OpenFile("priv.key").Read()
	if err != nil {
		panic(err)
	}

	privKey := asymmetric.LoadRSAPrivKey(string(readPrivKey))
	client := hls_settings.InitClient(privKey)

	if len(os.Args) < 2 {
		panic("len os.Args < 2")
	}

	switch os.Args[1] {
	case "w", "write":
		if len(os.Args) != 3 {
			panic("len os.Args != 3")
		}

		msg, err := client.Encrypt(
			privKey.PubKey(),
			payload.NewPayload(pldHead, []byte(os.Args[2])),
		)
		if err != nil {
			panic(err)
		}

		if err := hltClient.PutMessage(msg); err != nil {
			panic(err)
		}
	case "r", "read":
		if len(os.Args) != 3 {
			panic("len os.Args != 3")
		}

		msg, err := hltClient.GetMessage(os.Args[2])
		if err != nil {
			panic(err)
		}

		pubKey, pld, err := client.Decrypt(msg)
		if err != nil {
			panic(err)
		}

		if pld.Head() != pldHead {
			panic("payload head != constant head")
		}

		if pubKey.Address().String() != client.PubKey().Address().String() {
			panic("public key is incorrect")
		}

		fmt.Println(string(pld.Body()))
	case "h", "hashes":
		hashes, err := hltClient.GetHashes()
		if err != nil {
			panic(err)
		}

		for i, hash := range hashes {
			fmt.Printf("%d. %s\n", i, hash)
		}
	}
}

Для того, чтобы код успешно исполнялся - необходимо сгенерировать приватный ключ формата HLS. Для этого можно использовать следующий код. Длину ключа необходимо выбрать с учётом параметром HLS = 4096 бит (используется алгоритм RSA).

Генерация ключей
func main() {
	if len(os.Args) != 3 {
		panic(fmt.Sprintf(
			"usage: \n\t%s",
			"./main [key-size] [inline|pretty]",
		))
	}

	keySize, err := strconv.Atoi(os.Args[1])
	if err != nil {
		panic(err)
	}

	if keySize < 0 {
		panic("key size is negative")
	}

	mode := strings.ToLower(os.Args[2])
	if mode != "inline" && mode != "pretty" {
		panic("undefined mode [inline|pretty]")
	}

	priv := asymmetric.NewRSAPrivKey(uint64(keySize))
	if priv == nil {
		panic("generate key error")
	}

	switch mode {
	case "inline":
		filesystem.OpenFile("priv.key").Write([]byte(priv.String()))
		filesystem.OpenFile("pub.key").Write([]byte(priv.PubKey().String()))
	case "pretty":
		filesystem.OpenFile("priv.key").Write([]byte(priv.Format()))
		filesystem.OpenFile("pub.key").Write([]byte(priv.PubKey().Format()))
	}
}

Можно использовать любую опцию, будь то inline или pretty. Данный параметр лишь приводит к разному отображению ключей, не влияя на функциональность.

Пример приватного и публичного ключей.

Priv(go-peer/rsa){308209290201000282020100BBF3D8116285F28040C566404D20F94E54EF8511B623C4DCE87A7F050E181DF2440AEA59162CBED2BD792A31280C4F5348F9921E37D294A9CBD5F32B37B741A5CCE7F841B5F0D460E2D409CB5BC5B5C21478BC564D8FD0FC7BD4AB6591B813AFD6FF48053C8FE3D8F655D8531ED4ECF62DEB2EE185E7CD8D25C599F357F4FBAE8BCC646C962299DD448DA0F4B27C23488BBE22F87DF868555165F965C7F45DFBB460FF2F30EC3332E632698C3E1E3B92E1D87173D47B6F9513AC896F77E57EAB8CB5029566BA046B75A2F184DEAFB9D591E610F1F818F93E26DE02A8E1874D13C0A7F606FCA5856F7654CC36EDFB606E3EED57B101B9643C382FFC0B5EF060B3C45935D628FF2D39298E9F44F5D821EB56EDA2F8D232B4D6D8F959D3D6234205A1825AFE40F5BF6715BA4228EF097D2FE44C02F2AD7904935B992884AC03920E3BB05456C96AD6D7122E044C26B31235427D0ACE634EF7B57440F28E871680E7922E8B5B469F365E6A6F2F22749C6C06075A800ECE8AD09B2D51A46933CD029DA59A5E316570868F6C22C98B9B29E1AA089C3E9F96F8ACAF88B1FE60F72673FD402D146B614E4A62CF40C94C61930E0E5FA2C716E96EB19FA84DB27B500A0025D08671C00C2EF446F8775B2406C47C2A4FD063468781B9A3193835EF6F8BBFADFC7E60AE9C9EB642A0EFCB66C5E3ACB5E4995C8431A60822F55DE4AEA9C3E7CD0203010001028202010097DE69996A8DA72C00AA407BF6968D3BC11FA4DB71DC272704E66E1A4C902A2A66D6971392FA66DA6BE52AB57B91AECB322D0EE741DDF2E7DC5FAD541F7A011E929A619BDF0290E9147F96C66E0A2C5A7D262DB0628DB1A541FDCEDC8C493C071A673DEF954703297DAA51D8A01216C2B8C9CF20C58D4F271CFD34EF4F041215B0BA5FEEE9C55DA44FCF4CDE4C164880470244C99B280C4DDC2ED4BD6C3B4571AC07B04A967254D06CF7111813DD63EFC4739B00A8117150F90BB22592B0AA90BD6A498324F56BBAD4DBEBE19BFA4BE38937683BCC678985B1EC3423F7CFD6F4FDF7F9B11D963562A22FD4AF11CECBD5691D687D0B529CEE97CC1611AE0DF76407A350E6E42088A5FFF6B9F6A39CC0AA6A1AD6B75B9E5EDCE5BDFD405E59D922DDD984C27B23A4FC337C4F1FAD35DE94359F2AED5B6363DF8400ABC156B5937189E9B2596309C68ED50B15489F7113B16B933B88D64A4E977177AA541ACA8859652EAA4B65AB2FA9C1DD317978332D4BD7D7B6C8D2149A04A771F3BB22AD82F4C5451C7E29F8EF1B3F031971E5C2DCF4ECC8383FD7780C3ED2671D6CCC8F7A8F50250161DDEFA9BF5C244A85A46F14086923F83950B4B91F8FF834EE495877158A9C3375BB080984372C8709DFA8596EDAE467C129DD7B7A2F1BCB8F9465B3240E8D2F8E6FB6229428C9C3520232555336075D5ACE2D9DCFF86EF7DB89E1F9F50282010100E40C5405AE72C8695F2D0C3B2B5F4EE1E524FC5E39B5B73C3DD749E5E6B8935F5FD763DC8B4925449F9CD05DF00DD8415B35B521345738932CC29DFDA8C2EA8892F96FCBA708EA2C5A73879ACEB7EAB4F6ACE23FAB354E388205B6279F40E11ECDBAEDE5B2FCCD5CED935C84A5B050D8328DF8B984F1906B346D792AABD713DAE762605540CDFDB1763B362A647014C29FD0A2949F2E88730561B53316A0C83EAB751947CA3E81AFDA5A2C1A37978A2E20670A888586D40FFC9FDA8B687C87C14D2015CFD4090A171BF5F222DD79AF1C43658E28B5C65B6C92599AA8C461C99E854EBE8F28C45A7676E7CCFDDAC57A1195D43F2AB040918694D320537242F6D30282010100D2FD661BCFB50F9ACC84EC4E79486FC38D2645C81E5B210110DF70DC30C18864689EE69E23D8DFBAD74333C4DFCB588A36990683C522172B6AA959F017EF1F8FD7DA3C28EA025053E54D3FF386C74A9607A302ABB9CAA2EDDC4AF7BC9DF6A0901B5DF9771AC142D3287C6A930C796686B6C185D3BD1F27C4A4D327FC4B62441F90B1DB0BFEDABBA0516318327714BFD4053059EA0C3986696E18A124F8EB637F3822B5C2035F165672B9843CA0287B4246A5B65052B6FC9C49A5435B89E8A22086774D90FB3B77DFC0F73D7E74014F43FCEC11C14EA6D980EB3FB96945CE884C88AA6B42A29F3A9BD1C9524CE92EBE3ABAD7B851213FF7F58DDBE8C2CC31C2DF0282010100AC9EC85B2499AE2A54E8FC812D6B8F825EB9886EAAB06A6B274477A396E9D0643960092F401FB61E23D3C7C5D39827421190ADF2DC73C9575E6252C557A17376BE0A221DBDEA7997E93FDA3DCC8C5B1570439D5F2471CD2BE8FFF9CA343A202DF8FCAE6758FD13566C5F4781010A5DA7C95CBE1170188A811BC6621972C9F3359840573B313ABBF640D0C147858F71A780E1A4AF6B6F05CA4B2092D08128636B4CCDEEBCF0D22FC8B537DC7A5BEA51A668D096A090B26683536E8D3749783A2A8FFEE183D7B49FF6AE2B9585DEAFE565377F0004C0D3CD107A176982A6647F018756FBD7D1BC0FDDEC53BA4FCDF1FD5CA2556F1B191F4708E04E90019A5426110282010054E6EB23DDDD3A3E4C7660B0E35526C44614549846BEDCF514D9150D46015AE52CC46480D663FB03B083B65D4EC1010BFD146EEEAF711C69B82E15A6EBBAA9B4B4637939ED90A8CA59C75028AADF9F3FE82ACC7455FB18BD269C743C4E446430191323C3ADDC7F0B818B2F47A509651C68864F57F90E846E38F54F6DD2D7E419DF9BF24D4D6CAF7BBFC464844A2EEB52621426DA6BB8EA419AB006885E323A83830F549C91C6BDB263D00B4A6A18602137548F9C9AEC4F928480202EC1BBFF219F7A7A9810C0C9681CBA60E075C5E123B6AF3825D5848699BE109287AE3381DD6A51498032F8681933BE138427814BC34E5468331D72245187982A7DD8B55D79028201003EBAAE6BF47BAA6F01FE4BF57E56D8539F83C58B85BFAC35C44B3FF10DE2660C873837925049A1D6F237C86292BDFE86980030E3977296FD7CBEC3616290AA701CD3110355B2FDF4490154E911FE236F976AE6B1F7E9545D5C193F5C65A702CDC902FF6196C6DED34DEC6C0A11D4115A63616FE824CA44E90852E0FA82FF78D2FB10F2F6C586F3D8CCC36C5128816F24724B452D617D6AB943C49331F24650681FD5ABB08C29825BE5E731B21B310626E177E3D46FFE333877AA5D6D919FB05F45CBBCD808A5D26A58D3BCE1241F34C300B65D7C5D604524DDE96C64052AD0EE454A4059BAE6D36488F1107203F79EDF9A379DAF4EB290F2A3B6C6E43EA9777E}
Pub(go-peer/rsa){3082020A0282020100BBF3D8116285F28040C566404D20F94E54EF8511B623C4DCE87A7F050E181DF2440AEA59162CBED2BD792A31280C4F5348F9921E37D294A9CBD5F32B37B741A5CCE7F841B5F0D460E2D409CB5BC5B5C21478BC564D8FD0FC7BD4AB6591B813AFD6FF48053C8FE3D8F655D8531ED4ECF62DEB2EE185E7CD8D25C599F357F4FBAE8BCC646C962299DD448DA0F4B27C23488BBE22F87DF868555165F965C7F45DFBB460FF2F30EC3332E632698C3E1E3B92E1D87173D47B6F9513AC896F77E57EAB8CB5029566BA046B75A2F184DEAFB9D591E610F1F818F93E26DE02A8E1874D13C0A7F606FCA5856F7654CC36EDFB606E3EED57B101B9643C382FFC0B5EF060B3C45935D628FF2D39298E9F44F5D821EB56EDA2F8D232B4D6D8F959D3D6234205A1825AFE40F5BF6715BA4228EF097D2FE44C02F2AD7904935B992884AC03920E3BB05456C96AD6D7122E044C26B31235427D0ACE634EF7B57440F28E871680E7922E8B5B469F365E6A6F2F22749C6C06075A800ECE8AD09B2D51A46933CD029DA59A5E316570868F6C22C98B9B29E1AA089C3E9F96F8ACAF88B1FE60F72673FD402D146B614E4A62CF40C94C61930E0E5FA2C716E96EB19FA84DB27B500A0025D08671C00C2EF446F8775B2406C47C2A4FD063468781B9A3193835EF6F8BBFADFC7E60AE9C9EB642A0EFCB66C5E3ACB5E4995C8431A60822F55DE4AEA9C3E7CD0203010001}

Далее, нам необходимо добавить конфиг для HLT и запустить сервис.

Конфиг HLT

Файл hlt.cfg

{
    "address": "localhost:9573"
}

Адрес используется для принятия запросов по API. На данном примере видно, что мы игнорируем поле connection, которое используется для подключения к HLS.

$ ./hlt
> Service is running...

Теперь проверяем работу клиента.

$ go run ./main.go w 'hello, world!' # Сохраняем сообщение в HLT
$ go run ./main.go h # Проверяем существование сообщения
> 0. 49d45fddcb41984bdbe5e4c36eeb05d0c86495be4aee9ac3846259fc49d1dcfe
$ go run ./main.go r 49d45fddcb41984bdbe5e4c36eeb05d0c86495be4aee9ac3846259fc49d1dcfe # Получаем сохранённое сообщение
> hello, world!

Если сообщение было сгенерировано другим пользователем и мы попытаемся получить его (т.к. хеши всех сообщений нам известны), то приложение-клиент выдаст панику, потому что не сможет его расшифровать. Задача сводится к тому, что необходимо обладать нужным приватным ключом, а это может быть проблемой. Ровно по такой же причине и сам HLT не может узнать, что там конкретно хранится. Плюс к этому, формат HLS всегда приводит сообщения к константному размеру, а поэтому проанализировать содержание по величине также будет проблематичным действием.

Заключение

На этом пожалуй всё. HLT является крайне примитивным приложением, тем не менее, оно способно порождать несколько способов использования, как со стороны HLS, так и со стороны функций сторонних клиент-безопасных приложений.

Все исходные коды находятся в полностью открытом доступе, их можно найти тут (HLS), тут (HLM) и тут (HLT). Описанный выше пример с клиентом находится тут. Работы по анализу анонимности, и в частности HLS, находятся тут, тут и тут.

Также, если остались какие-либо вопросы связанные с анонимностью и/или безопасностью вышеописанных приложений, то можете их задать в комментариях. С радостью на них отвечу.

Комментарии (0)