Введение
Анонимная сеть Hidden Lake является в своей области уникальным и достаточно своеобразным проектом, т.к. базируется на совершенно иных методах и подходах анонимизации трафика, чем большинство ныне нам известных сетей. Из-за того, что сеть является относительно новой — она часто дополняется и совершенствуется. Одним из таковых дополнений стал новый способ запуска узла HL.
Немного о сети Hidden Lake
Анонимная сеть Hidden Lake (HL) — это децентрализованная F2F (friend-to-friend) анонимная сеть с теоретической доказуемостью. В отличие от известных анонимных сетей, подобия Tor, I2P, Mixminion, Crowds и т.п., сеть HL способна противостоять атакам глобального наблюдателя. Сети Hidden Lake для анонимизации своего трафика не важны такие критерии как: 1) уровень сетевой централизации, 2) количество узлов, 3) расположение узлов и 4) связь между узлами в сети.
Теоретическая доказуемость сети HL сводится к QB-задаче (задаче на базе очередей). Данная задача может быть описана следующим списком действий:
-
Каждое сообщение m шифруется ключом получателя k: c = Ek(m),
-
Сообщение c отправляется в период = T всем участникам сети,
-
Период T одного участника независим от периодов T1, T2, …, Tn других участников,
-
Если на период T сообщения не существует, то в сеть отправляется ложное сообщение v без получателя (со случайным ключом r): c = Er(v),
-
Каждый участник пытается расшифровать принятое им сообщение из сети: m = Dk(c).
При такой модели глобальный наблюдатель будет видеть лишь факт генерации шифртекстов C = {c1, c2, …, cn} в определённо заданные периоды времени = T без возможности дальнейшего различия истинности Ek(m) или ложности Er(v) выбираемых им шифртекстов.
Более подробный анализ анонимной сети Hidden Lake, а также QB-задачи, можно найти в исследовательской работе: Анонимная сеть «Hidden Lake».
Возвращаемся к проблеме
Ранее существовало два способа того, как можно поднять узел Hidden Lake — либо использовать низкоуровневые функции и методы из пакета go-peer, чтобы с нуля сконструировать полноценную ноду, либо использовать высокоуровневый подход и запускать готовое приложение HLS (сервис анонимизации трафика), которое будет общаться с прикладными сервисами.
Для большинства случаев, когда нужно просто использовать функциональность сети — второй вариант конечно является наиболее релевантным. Тем не менее, когда хочется что-либо запрограммировать, проверить или протестировать, так чтобы, это ещё и успешно работало с сетью, то использование связки сервисов во главе с HLS будет являться избыточным, особенно когда нужно запрограммировать что-либо на родном языке Hidden Lake — языке Go. С другой стороны, использование пакета go-peer для реконструкции уже существующего и полностью работающего механизма HLS является ещё куда более нежелательным и избыточным способом.
Таким образом, перенос функциональности HLS в экспортируемую область позволит решить данное противоречие. И в этой статье мы посмотрим как стало возможно работать с сетью Hidden Lake средствами языка Go после переноса этой самой функциональности.
Низкоуровневый подход (go-peer)
Чтобы успешно связаться с другим узлом, посредством примитивов пакета go-peer, требуется сконструировать собственный узел как из деталек лего. Так например, чтобы создать экземпляр полноценного узла Hidden Lake, требуется написать примерно следующее:
Конструктор узла
networkMask := uint32(0x5f67705f) msgSizeBytes := uint64(8 << 10) netMsgSettings := net_message.NewSettings(&net_message.SSettings{ FWorkSizeBits: 18, FNetworkKey: "oi4r9NW9Le7fKF9d", }) node := anonymity.NewNode( anonymity.NewSettings(&anonymity.SSettings{ FNetworkMask: networkMask, FServiceName: "service", FFetchTimeout: time.Minute, }), logger.NewLogger( logger.NewSettings(&logger.SSettings{ FInfo: os.Stdout, FWarn: os.Stdout, FErro: os.Stderr, }), func(pLogArg logger.ILogArg) string { logFactory, ok := pLogArg.(anon_logger.ILogGetterFactory) if !ok { panic("got invalid log arg") } logGetter := logFactory.Get() return encoding.HexEncode(logGetter.GetHash()) }, ), func() database.IKVDatabase { db, err := database.NewKVDatabase(dbPath) if err != nil { panic(err) } return db }(), network.NewNode( network.NewSettings(&network.SSettings{ FAddress: "localhost:9571", FMaxConnects: 256, FReadTimeout: 5*time.Second, FWriteTimeout: 5*time.Second, FConnSettings: conn.NewSettings(&conn.SSettings{ FMessageSettings: netMsgSettings, FLimitMessageSizeBytes: msgSizeBytes, FWaitReadTimeout: time.Hour, FDialTimeout: 5*time.Second, FReadTimeout: 5*time.Second, FWriteTimeout: 5*time.Second, }), }), cache.NewLRUCache(2 << 10), ), queue.NewQBProblemProcessor( queue.NewSettings(&queue.SSettings{ FMessageConstructSettings: net_message.NewConstructSettings(&net_message.SConstructSettings{ FSettings: netMsgSettings, FParallel: 1, }), FNetworkMask: networkMask, FQueuePeriod: 5*time.Second, FPoolCapacity: [2]uint64{256, 32}, }), func() client.IClient { client := client.NewClient(asymmetric.NewPrivKey(), msgSizeBytes) if client.GetPayloadLimit() <= encoding.CSizeUint64 { panic(`client.GetPayloadLimit() <= encoding.CSizeUint64`) } return client }(), ), asymmetric.NewMapPubKeys(), ).HandleFunc( 0x5f686c5f, func( pCtx context.Context, pNode anonymity.INode, pSender asymmetric.IPubKey, pReqBytes []byte, ) ([]byte, error) { loadReq, err := request.LoadRequest(pReqBytes) if err != nil { return nil, errors.Join(ErrLoadRequest, err) } service, ok := servicesMap[loadReq.GetHost()] if !ok { return nil, ErrUndefinedService } pushReq, err := http.NewRequestWithContext( pCtx, loadReq.GetMethod(), fmt.Sprintf("http://%s%s", service, loadReq.GetPath()), bytes.NewReader(loadReq.GetBody()), ) if err != nil { return nil, errors.Join(ErrBuildRequest, err) } for key, val := range loadReq.GetHead() { pushReq.Header.Set(key, val) } pushReq.Header.Set(hls_settings.CHeaderPublicKey, pSender.ToString()) httpClient := &http.Client{Timeout: time.Minute} resp, err := httpClient.Do(pushReq) if err != nil { logger.PushWarn(logBuilder.WithType(internal_anon_logger.CLogWarnRequestToService)) return nil, errors.Join(ErrBadRequest, err) } defer resp.Body.Close() respMode := resp.Header.Get(hls_settings.CHeaderResponseMode) switch respMode { case "", hls_settings.CHeaderResponseModeON: return response.NewResponse(resp.StatusCode). WithHead(getResponseHead(resp)). WithBody(getResponseBody(resp)). ToBytes(), nil case hls_settings.CHeaderResponseModeOFF: return nil, nil default: return nil, ErrInvalidResponseMode } }, ) connKeeper := connkeeper.NewConnKeeper( connkeeper.NewSettings(&connkeeper.SSettings{ FDuration: 10*time.Second, FConnections: func() []string{ return getConnections() }, }), node.GetNetworkNode(), ) ...
Если человек не знаком с Hidden Lake, то такая простыня кода его скорее отпугнёт, чем заинтересует. Поэтому существует, как минимум, ещё один — высокоуровневый подход.
Высокоуровневый подход (HLS)
С высокоуровневым подходом всё куда проще — нужно лишь установить HLS (сервис анонимизации) и далее запустить его с ключом сети.
$ go install github.com/number571/hidden-lake/cmd/hls@latest $ hls -network=oi4r9NW9Le7fKF9d
Данный подход работает потому как в репозитории проекта Hidden Lake по умолчанию вшит список работающих ретрансляторов. Даже если ретрансляторы в будущем будут скомпрометированы, то из-за применяемой QB-задачи ничего плохого не произойдёт — анонимизация трафика нарушена не будет.
Среднеуровневый подход
Новый подход позволяет избавиться от излишних подробностей низкоуровневого подхода (настроек и конструктов) и от необходимости использования сервисов в высокоуровневом подходе. Иными словами, данный подход идеален, когда: 1) нужно запрограммировать что-либо конкретно связанное с сетью Hidden Lake без использования микросервисной архитектуры, 2) нет сил, возможности или желания разбираться в низкоуровневых деталях работы. Получаем в итоге своеобразное API.
Конструктор узла
node := network.NewHiddenLakeNode( const networkKey = "oi4r9NW9Le7fKF9d" network.NewSettingsByNetworkKey(networkKey, nil), asymmetric.NewPrivKey(), func() database.IKVDatabase { kv, err := database.NewKVDatabase(dbPath + ".db") if err != nil { panic(err) } return kv }(), func() []string { network := hiddenlake.GNetworks[networkKey] conns := make([]string, 0, len(network.FConnections)) for _, c := range network.FConnections { conns = append(conns, fmt.Sprintf("%s:%d", c.FHost, c.FPort)) } return conns }, func(_ context.Context, _ asymmetric.IPubKey, r request.IRequest) (response.IResponse, error) { rsp := []byte(fmt.Sprintf("echo: %s", string(r.GetBody()))) return response.NewResponse().WithBody(rsp), nil }, ) ...
Проверяем работу
Всё, что нам остаётся — это проверить работу нового подхода на практике. Для этого достаточно будет запустить два узла, которые будут между собой как-либо взаимодействовать. Одним из наиболее простых способов взаимодействия является реализация echo-сервиса, при котором один узел будет отправлять запрос, а другой соответственно отвечать.
package main import ( ... ) func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Запускаем два узла var ( node1 = runNode(ctx, "node1") node2 = runNode(ctx, "node2") ) // Узлы обмениваются публичными ключами _, pubKey := exchangeKeys(node1, node2) for { // Запрос с ожиданием ответа rsp, err := node1.FetchRequest( ctx, pubKey, request.NewRequest().WithBody([]byte("hello, world!")), ) if err != nil { fmt.Println("error:(%s)\n", err.Error()) continue } fmt.Printf("response:(%s)\n", string(rsp.GetBody())) } } ...
Функции runNode & exchangeKeys
func runNode(ctx context.Context, dbPath string) network.IHiddenLakeNode { const networkKey = "oi4r9NW9Le7fKF9d" node := network.NewHiddenLakeNode( // Задаём настройки узла исходя из ключа сети network.NewSettingsByNetworkKey(networkKey, nil), // Приватный ключ - идентификатор узла asymmetric.NewPrivKey(), // База данных нужна для сохранения хешей принимаемых шифртекстов func() database.IKVDatabase { kv, err := database.NewKVDatabase(dbPath + ".db") if err != nil { panic(err) } return kv }(), // Подключаем все соединения из build/networks.yml по ключу сети func() []string { network := hiddenlake.GNetworks[networkKey] conns := make([]string, 0, len(network.FConnections)) for _, c := range network.FConnections { conns = append(conns, fmt.Sprintf("%s:%d", c.FHost, c.FPort)) } return conns }, // Пример запуска простого echo-сервиса func(_ context.Context, _ asymmetric.IPubKey, r request.IRequest) (response.IResponse, error) { rsp := []byte(fmt.Sprintf("echo: %s", string(r.GetBody()))) return response.NewResponse().WithBody(rsp), nil }, ) // Запускаем узел на // 1. генерацию шифрованного трафика // 2. подключение к ретрансляторам go func() { _ = node.Run(ctx) }() return node }
func exchangeKeys(hlNode1, hlNode2 network.IHiddenLakeNode) (asymmetric.IPubKey, asymmetric.IPubKey) { node1 := hlNode1.GetOrigNode() node2 := hlNode2.GetOrigNode() pubKey1 := node1.GetMessageQueue().GetClient().GetPrivKey().GetPubKey() pubKey2 := node2.GetMessageQueue().GetClient().GetPrivKey().GetPubKey() node1.GetMapPubKeys().SetPubKey(pubKey2) node2.GetMapPubKeys().SetPubKey(pubKey1) return pubKey1, pubKey2 }
Запускаем и проверяем. Полный исходный код данного примера можно найти тут.
$ go run . > response:(echo: hello, world!) response:(echo: hello, world!) response:(echo: hello, world!) response:(echo: hello, world!) response:(echo: hello, world!) response:(echo: hello, world!) response:(echo: hello, world!)
Заключение
В результате, мы смогли запустить два полноценно работающих узла в сети Hidden Lake, обменивающихся между собой сообщениями без использования низкоуровневых примитивов пакета go-peer и высокоуровневого подхода связки HL сервисов.
ссылка на оригинал статьи https://habr.com/ru/articles/858110/
Добавить комментарий