diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index cbd88780..fe0b6b7e 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -5,7 +5,7 @@ # documentation. # Sample workflow for building and deploying a Jekyll site to GitHub Pages -name: Deploy Jekyll site to Pages +name: Build OpenNHP Docs Website on: push: diff --git a/.github/workflows/ubuntu-build.yml b/.github/workflows/ubuntu-build.yml new file mode 100644 index 00000000..f37fbac8 --- /dev/null +++ b/.github/workflows/ubuntu-build.yml @@ -0,0 +1,28 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: Build and Test Code on Ubuntu + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21' + + - name: Build + run: make + + - name: Test + run: make test diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..afbe7e53 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +opennhp@gmail.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/Makefile b/Makefile index 40e0ce01..804b915b 100644 --- a/Makefile +++ b/Makefile @@ -78,9 +78,13 @@ endif plugins: @if test -d $(NHP_PLUGINS); then $(MAKE) -C $(NHP_PLUGINS); fi +test: + @echo "[OpenNHP] Runing Tests for the Output Binaries ..." + @echo "$(COLOUR_GREEN)[OpenNHP] All Tests Are Done!$(END_COLOUR)" + archive: - @echo "$(COLOUR_BLUE)[opennhp] Start archiving... $(END_COLOUR)" + @echo "$(COLOUR_BLUE)[OpenNHP] Start archiving... $(END_COLOUR)" @cd release && mkdir -p archive && tar -czvf ./archive/$(PACKAGE_FILE) nhp-agent nhp-ac nhp-server - @echo "$(COLOUR_GREEN)[opennhp] Package ${PACKAGE_FILE} archived!$(END_COLOUR)" + @echo "$(COLOUR_GREEN)[OpenNHP] Package ${PACKAGE_FILE} archived!$(END_COLOUR)" -.PHONY: all generate-version-and-build init agentsdk devicesdk plugins archive +.PHONY: all generate-version-and-build init agentsdk devicesdk plugins test archive diff --git a/README.de.md b/README.de.md index e69de29b..54369bf3 100644 --- a/README.de.md +++ b/README.de.md @@ -0,0 +1,213 @@ +[![en](https://img.shields.io/badge/lang-en-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.md) +[![zh-cn](https://img.shields.io/badge/lang-zh--cn-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.zh-cn.md) +[![de](https://img.shields.io/badge/lang-de-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.de.md) +[![ja](https://img.shields.io/badge/lang-ja-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.ja.md) +[![fr](https://img.shields.io/badge/lang-fr-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.fr.md) +[![es](https://img.shields.io/badge/lang-es-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.es.md) + +![OpenNHP Logo](docs/images/logo11.png) +# OpenNHP: Zero Trust Netzwerk-Infrastruktur-Verbergungsprotokoll +Ein leichtgewichtiges, kryptographisch getriebenes Zero Trust Netzwerkprotokoll auf der OSI-Schicht 5, um Ihren Server und Ihre Daten vor Angreifern zu verbergen. + +![Build Status](https://img.shields.io/badge/build-passing-brightgreen) +![Version](https://img.shields.io/badge/version-1.0.0-blue) +![Lizenz](https://img.shields.io/badge/license-Apache%202.0-green) + +--- + +## Herausforderung: KI verwandelt das Internet in einen "Dunklen Wald" + +Der schnelle Fortschritt der **KI**-Technologien, insbesondere großer Sprachmodelle (LLMs), verändert die Cybersicherheitslandschaft erheblich. Das Aufkommen der **autonomen Ausnutzung von Schwachstellen (AVE)** stellt einen großen Fortschritt im KI-Zeitalter dar, indem es die Ausnutzung von Schwachstellen automatisiert, wie in [diesem Forschungspapier](https://arxiv.org/abs/2404.08144) gezeigt wird. Diese Entwicklung erhöht das Risiko für alle exponierten Netzwerkdienste erheblich und erinnert an die [Dunkle Wald-Hypothese](https://de.wikipedia.org/wiki/Dunkler_Wald) des Internets. KI-gesteuerte Tools scannen kontinuierlich die digitale Umgebung, identifizieren schnell Schwachstellen und nutzen sie aus. Folglich entwickelt sich das Internet zu einem **"dunklen Wald"**, in dem **Sichtbarkeit Verwundbarkeit bedeutet**. + +![Verwundbarkeitsrisiken](docs/images/Vul_Risks.png) + +Gartner prognostiziert einen [schnellen Anstieg von KI-gesteuerten Cyberangriffen](https://www.gartner.com/en/newsroom/press-releases/2024-08-28-gartner-forecasts-global-information-security-spending-to-grow-15-percent-in-2025). Dieser Wandel erfordert eine Neubewertung traditioneller Cybersicherheitsstrategien mit einem Fokus auf proaktive Verteidigungsmaßnahmen, schnelle Reaktionsmechanismen und die Einführung von Netzwerkverbergungstechnologien zum Schutz kritischer Infrastrukturen. + +--- + +## Schnelle Demo: OpenNHP in Aktion sehen + +Bevor wir in die Details von OpenNHP eintauchen, beginnen wir mit einer kurzen Demonstration, wie OpenNHP einen Server vor unbefugtem Zugriff schützt. Sie können dies in Aktion sehen, indem Sie den geschützten Server unter https://acdemo.opennhp.org aufrufen. + +### 1) Der geschützte Server ist für nicht authentifizierte Benutzer "unsichtbar" + +Standardmäßig führt jeder Versuch, eine Verbindung zum geschützten Server herzustellen, zu einem TIME OUT-Fehler, da alle Ports geschlossen sind, wodurch der Server *"unsichtbar"* und scheinbar offline wird. + +![OpenNHP Demo](docs/images/OpenNHP_ACDemo0.png) + +Das Scannen der Ports des Servers führt ebenfalls zu einem TIME OUT-Fehler. + +![OpenNHP Demo](docs/images/OpenNHP_ScanDemo.png) + +### 2) Nach der Authentifizierung wird der geschützte Server zugänglich + +OpenNHP unterstützt eine Vielzahl von Authentifizierungsmethoden, wie OAuth, SAML, QR-Codes und mehr. Für diese Demonstration verwenden wir einen einfachen Benutzernamen/Passwort-Authentifizierungsdienst unter https://demologin.opennhp.org. + +![OpenNHP Demo](docs/images/OpenNHP_DemoLogin.png) + +Sobald Sie auf die Schaltfläche "Login" klicken, ist die Authentifizierung erfolgreich und Sie werden zum geschützten Server weitergeleitet. Zu diesem Zeitpunkt wird der Server *"sichtbar"* und auf Ihrem Gerät zugänglich. + +![OpenNHP Demo](docs/images/OpenNHP_ACDemo1.png) + +--- + +## Vision: Das Internet vertrauenswürdig machen + +Die Offenheit der TCP/IP-Protokolle hat das explosive Wachstum von Internetanwendungen vorangetrieben, aber auch Schwachstellen offengelegt, die es böswilligen Akteuren ermöglichen, unbefugten Zugriff zu erhalten und jede exponierte IP-Adresse auszunutzen. Obwohl das [OSI-Netzwerkmodell](https://de.wikipedia.org/wiki/OSI-Modell) die *5. Schicht (Sitzungsschicht)* zur Verwaltung von Verbindungen definiert, wurden bisher nur wenige effektive Lösungen hierfür implementiert. + +**NHP**, oder das **"Netzwerk-Infrastruktur-Verbergungsprotokoll"**, ist ein leichtgewichtiges, kryptographisch getriebenes Zero Trust Netzwerkprotokoll, das auf der *OSI-Sitzungsschicht* arbeitet und sich ideal zur Verwaltung der Netzwerkvisibilität und Verbindungen eignet. Das Hauptziel von NHP ist es, geschützte Ressourcen vor unbefugten Entitäten zu verbergen und den Zugriff nur verifizierten, autorisierten Benutzern durch kontinuierliche Überprüfung zu gewähren, um so zu einem vertrauenswürdigeren Internet beizutragen. + +![Vertrauenswürdiges Internet](docs/images/TrustworthyCyberspace.png) + +--- + +## Lösung: OpenNHP stellt die Kontrolle über die Netzwerkvisibilität wieder her + +**OpenNHP** ist die Open-Source-Implementierung des NHP-Protokolls. Es basiert auf der Kryptographie und wurde mit Sicherheitsprinzipien im Vordergrund entwickelt, um eine echte Zero Trust-Architektur auf der *OSI-Sitzungsschicht* zu implementieren. + +![OpenNHP als OSI 5. Schicht](docs/images/OSI_OpenNHP.png) + +OpenNHP baut auf früheren Forschungen zur Netzwerkverbergungstechnologie auf und nutzt moderne kryptographische Rahmenwerke und Architektur, um Sicherheit und hohe Leistung zu gewährleisten und die Einschränkungen früherer Technologien zu überwinden. + +| Netzwerk-Infrastruktur-Verbergungsprotokoll | 1. Generation | 2. Generation | 3. Generation | +|:---|:---|:---|:---| +| **Kerntechnologie** | [Port Knocking](https://de.wikipedia.org/wiki/Port_knocking) | [Single Packet Authorization (SPA)](https://cloudsecurityalliance.org/artifacts/software-defined-perimeter-zero-trust-specification-v2) | Netzwerk-Infrastruktur-Verbergungsprotokoll (NHP) | +| **Authentifizierung** | Port-Sequenzen | Geteilte Geheimnisse | Modernes Kryptographie-Rahmenwerk | +| **Architektur** | Kein Kontrollplan | Kein Kontrollplan | Skalierbarer Kontrollplan | +| **Fähigkeit** | Ports verbergen | Ports verbergen | Ports, IPs und Domains verbergen | +| **Zugriffskontrolle** | IP-Ebene | Port-Ebene | Anwendungsebene | +| **Open-Source-Projekte** | [knock](https://github.com/jvinet/knock) *(C)* | [fwknop](https://github.com/mrash/fwknop) *(C++)* | [OpenNHP](https://github.com/OpenNHP/opennhp) *(Go)* | + +> Es ist entscheidend, eine **speichersichere** Sprache wie *Go* für die Entwicklung von OpenNHP zu wählen, wie im [technischen Bericht der US-Regierung](https://www.whitehouse.gov/wp-content/uploads/2024/02/Final-ONCD-Technical-Report.pdf) betont wird. Für einen detaillierten Vergleich zwischen **SPA und NHP** lesen Sie bitte die [Abschnitt unten](#comparison-between-spa-and-nhp). + +## Sicherheitsvorteile + +Da OpenNHP Zero Trust-Prinzipien auf der *OSI-Sitzungsschicht* implementiert, bietet es erhebliche Vorteile: + +- Reduziert die Angriffsfläche durch Verbergen der Infrastruktur +- Verhindert unbefugte Netzwerkaufklärung +- Mildert die Ausnutzung von Schwachstellen +- Verhindert Phishing durch verschlüsseltes DNS +- Schützt vor DDoS-Angriffen +- Ermöglicht granulare Zugriffskontrolle +- Bietet verbindungsbasierte Identitätsverfolgung +- Angriffszurechnung + +## Architektur + +Die Architektur von OpenNHP orientiert sich an der [NIST Zero Trust-Architektur](https://www.nist.gov/publications/zero-trust-architecture). Sie folgt einem modularen Design mit drei Hauptkomponenten: **NHP-Server**, **NHP-AC** und **NHP-Agent**, wie in der folgenden Abbildung dargestellt. + +![OpenNHP Architektur](docs/images/OpenNHP_Arch.png) + +> Weitere Informationen zur Architektur und zum Workflow finden Sie in der [OpenNHP-Dokumentation](https://opennhp.org/). + +## Kern: Kryptographische Algorithmen + +Kryptographie steht im Mittelpunkt von OpenNHP und bietet robuste Sicherheit, hervorragende Leistung und Skalierbarkeit durch den Einsatz modernster kryptographischer Algorithmen. Nachfolgend sind die wichtigsten kryptographischen Algorithmen und Frameworks aufgeführt, die von OpenNHP verwendet werden: + +- **[Elliptische Kurvenkryptographie (ECC)](https://de.wikipedia.org/wiki/Elliptische-Kurven-Kryptographie)**: Wird für effiziente asymmetrische Kryptographie verwendet. + +> Im Vergleich zu RSA bietet ECC eine höhere Effizienz mit stärkerer Verschlüsselung bei kürzeren Schlüssellängen, was sowohl die Netzwerkübertragung als auch die Rechenleistung verbessert. Die folgende Tabelle zeigt die Unterschiede in der Sicherheitsstärke, den Schlüssellängen und dem Verhältnis zwischen RSA und ECC sowie die jeweiligen Gültigkeitszeiträume. + +| Sicherheitsstärke (Bits) | DSA/RSA-Schlüssellänge (Bits) | ECC-Schlüssellänge (Bits) | Verhältnis: ECC zu DSA/RSA | Gültigkeit | +|:------------------------:|:-----------------------------:|:------------------------:|:--------------------------:|:---------:| +| 80 | 1024 | 160-223 | 1:6 | Bis 2010 | +| 112 | 2048 | 224-255 | 1:9 | Bis 2030 | +| 128 | 3072 | 256-383 | 1:12 | Nach 2031 | +| 192 | 7680 | 384-511 | 1:20 | | +| 256 | 15360 | 512+ | 1:30 | | + +- **[Noise Protocol Framework](https://noiseprotocol.org/)**: Ermöglicht sicheren Schlüsselaustausch, Nachrichtenverschlüsselung/-entschlüsselung und gegenseitige Authentifizierung. + +> Das Noise-Protokoll basiert auf dem [Diffie-Hellman-Schlüsselaustausch](https://de.wikipedia.org/wiki/Diffie-Hellman-Schl%C3%BCsselaustausch) und bietet moderne kryptographische Lösungen wie gegenseitige und optionale Authentifizierung, Identitätsverbergung, Vorwärtsgeheimnis und null Round-Trip-Verschlüsselung. Es hat sich bereits durch seine Sicherheit und Leistung bewährt und wird von beliebten Anwendungen wie [WhatsApp](https://www.whatsapp.com/security/WhatsApp-Security-Whitepaper.pdf), [Slack](https://github.com/slackhq/nebula) und [WireGuard](https://www.wireguard.com/) verwendet. + +- **[Identitätsbasierte Kryptographie (IBC)](https://de.wikipedia.org/wiki/Identit%C3%A4tsbasierte_Kryptographie)**: Vereinfacht die Schlüsselverteilung im großen Maßstab. + +> Eine effiziente Schlüsselverteilung ist entscheidend für die Umsetzung von Zero Trust. OpenNHP unterstützt sowohl PKI als auch IBC. Während PKI seit Jahrzehnten weit verbreitet ist, hängt es von zentralisierten Zertifizierungsstellen (CA) zur Identitätsprüfung und Schlüsselverwaltung ab, was zeitaufwändig und kostspielig sein kann. Im Gegensatz dazu ermöglicht IBC einen dezentralisierten und selbstverwalteten Ansatz für die Identitätsprüfung und Schlüsselverwaltung, was es kostengünstiger für die Zero Trust-Umgebung von OpenNHP macht, in der Milliarden von Geräten oder Servern in Echtzeit geschützt und eingebunden werden müssen. + +- **[Zertifikatslose Kryptographie (CL-PKC)](https://de.wikipedia.org/wiki/Zertifikatslose_Kryptographie)**: Empfohlener IBC-Algorithmus + +> CL-PKC ist ein Schema, das die Sicherheit verbessert, indem es die Schlüsselverwaltung vermeidet und die Einschränkungen der identitätsbasierten Kryptographie (IBC) angeht. In den meisten IBC-Systemen wird der private Schlüssel eines Benutzers von einer Schlüsselgenerierungsstelle (KGC) erstellt, was erhebliche Risiken birgt. Ein kompromittierter KGC kann zur Offenlegung der privaten Schlüssel aller Benutzer führen, wodurch volles Vertrauen in den KGC erforderlich ist. CL-PKC mindert dieses Problem, indem der Schlüsselerstellungsprozess aufgeteilt wird, sodass der KGC nur einen Teil des privaten Schlüssels kennt. Dadurch kombiniert CL-PKC die Stärken von PKI und IBC und bietet eine stärkere Sicherheit ohne die Nachteile der zentralisierten Schlüsselverwaltung. + +Weiterführende Informationen: + +> Weitere Details zu den in OpenNHP verwendeten kryptographischen Algorithmen finden Sie in der [OpenNHP-Dokumentation](https://opennhp.org/cryptography/). + +## Hauptfunktionen + +- Mildert die Ausnutzung von Schwachstellen, indem standardmäßig "deny-all"-Regeln angewendet werden +- Verhindert Phishing-Angriffe durch verschlüsselte DNS-Auflösung +- Schützt vor DDoS-Angriffen, indem die Infrastruktur verborgen wird +- Ermöglicht Angriffszurechnung durch identitätsbasierte Verbindungen +- Standardmäßig verweigerter Zugriff auf alle geschützten Ressourcen +- Authentifizierung basierend auf Identität und Geräten vor dem Netzwerkzugang +- Verschlüsselte DNS-Auflösung, um DNS-Hijacking zu verhindern +- Verteilte Infrastruktur zur Minderung von DDoS-Angriffen +- Skalierbare Architektur mit entkoppelten Komponenten +- Integration mit bestehenden Systemen zur Verwaltung von Identitäten und Zugriffen +- Unterstützung für verschiedene Bereitstellungsmodelle (Client-zu-Gateway, Client-zu-Server usw.) +- Kryptographisch sicher unter Verwendung moderner Algorithmen (ECC, Noise Protocol, IBC) + +
+Klicken Sie hier, um die Funktionsdetails zu erweitern + +- **Standardmäßig verweigerter Zugriff**: Alle Ressourcen sind standardmäßig verborgen und werden nur nach Authentifizierung und Autorisierung zugänglich. +- **Authentifizierung basierend auf Identität und Geräten**: Stellt sicher, dass nur bekannte Benutzer auf zugelassenen Geräten Zugriff erhalten. +- **Verschlüsselte DNS-Auflösung**: Verhindert DNS-Hijacking und damit verbundene Phishing-Angriffe. +- **DDoS-Minderung**: Das verteilte Infrastruktursystem hilft beim Schutz vor DDoS-Angriffen. +- **Skalierbare Architektur**: Entkoppelte Komponenten ermöglichen flexiblen Einsatz und Skalierung. +- **IAM-Integration**: Funktioniert mit Ihren bestehenden Systemen zur Verwaltung von Identitäten und Zugriffen. +- **Flexibler Einsatz**: Unterstützt verschiedene Modelle, einschließlich Client-zu-Gateway, Client-zu-Server und mehr. +- **Starke Kryptographie**: Nutzt moderne Algorithmen wie ECC, Noise Protocol und IBC für robuste Sicherheit. +
+ +## Bereitstellung + +OpenNHP unterstützt mehrere Bereitstellungsmodelle für unterschiedliche Anwendungsfälle: + +- Client-zu-Gateway: Sichert den Zugriff auf mehrere Server hinter einem Gateway +- Client-zu-Server: Sichert direkt einzelne Server/Anwendungen +- Server-zu-Server: Sichert die Kommunikation zwischen Backend-Diensten +- Gateway-zu-Gateway: Sichert Standort-zu-Standort-Verbindungen + +> Weitere Details zur Bereitstellung finden Sie in der [OpenNHP-Dokumentation](https://opennhp.org/deploy/). + +## Vergleich zwischen SPA und NHP +Das Single Packet Authorization (SPA)-Protokoll ist in der vom [Cloud Security Alliance (CSA)](https://cloudsecurityalliance.org/) veröffentlichten [Software Defined Perimeter (SDP)-Spezifikation](https://cloudsecurityalliance.org/artifacts/software-defined-perimeter-zero-trust-specification-v2) enthalten. NHP verbessert die Sicherheit, Zuverlässigkeit, Skalierbarkeit und Erweiterbarkeit durch ein modernes kryptographisches Framework und eine moderne Architektur, wie im [AHAC-Forschungspapier](https://www.mdpi.com/2076-3417/14/13/5593) gezeigt. + +| - | SPA | NHP | Vorteile von NHP | +|:---|:---|:---|:---| +| **Architektur** | Das SPA-Paketentschlüsselungs- und Benutzer-/Geräteauthentifizierungskomponente ist mit der Netzwerkzugriffskontrollkomponente im SPA-Server gekoppelt. | NHP-Server (die Paketentschlüsselungs- und Benutzer-/Geräteauthentifizierungskomponente) und NHP-AC (die Zugriffskontrollkomponente) sind entkoppelt. Der NHP-Server kann auf separaten Hosts bereitgestellt werden und unterstützt horizontale Skalierung. | | +| **Kommunikation** | Einfache Richtung | Bidirektional | Bessere Zuverlässigkeit durch Statusbenachrichtigung der Zugriffskontrolle | +| **Kryptographisches Framework** | Geteilte Geheimnisse | PKI oder IBC, Noise Framework | | +| **Fähigkeit zur Verbergung der Netzwerkinfrastruktur** | Nur Serverports | Domains, IPs und Ports | Stärker gegen verschiedene Angriffe (z.B. Schwachstellen, DNS-Hijacking und DDoS-Angriffe) | +| **Erweiterbarkeit** | Keine, nur für SDP | Universell | Unterstützt jedes Szenario, das eine Dienstverschleierung erfordert | +| **Interoperabilität** | Nicht verfügbar | Anpassbar | NHP kann nahtlos mit bestehenden Protokollen (z.B. DNS, FIDO usw.) integriert werden | + +## Beitrag leisten + +Wir begrüßen Beiträge zu OpenNHP! Bitte lesen Sie unsere [Beitragsrichtlinien](CONTRIBUTING.md), um mehr darüber zu erfahren, wie Sie sich beteiligen können. + +## Lizenz + +OpenNHP wird unter der [Apache 2.0-Lizenz](LICENSE) veröffentlicht. + +## Kontakt + +- Projekt-Website: [https://github.com/OpenNHP/opennhp](https://github.com/OpenNHP/opennhp) +- E-Mail: [opennhp@gmail.com](mailto:opennhp@gmail.com) +- Slack-Kanal: [Treten Sie unserem Slack bei](https://slack.opennhp.org) + +Für eine detaillierte Dokumentation besuchen Sie bitte unsere [Offizielle Dokumentation](https://opennhp.org). + +## Referenzen + +- [Software-Defined Perimeter (SDP) Specification v2.0](https://cloudsecurityalliance.org/artifacts/software-defined-perimeter-zero-trust-specification-v2). Jason Garbis, Juanita Koilpillai, Junaid lslam, Bob Flores, Daniel Bailey, Benfeng Chen, Eitan Bremler, Michael Roza, Ahmed Refaey Hussein. [*Cloud Security Alliance (CSA)*](https://cloudsecurityalliance.org/). März 2022. +- [AHAC: Fortschrittliches Netzwerk-Verbergung-Zugriffskontroll-Framework](https://www.mdpi.com/2076-3417/14/13/5593). Mudi Xu, Benfeng Chen, Zhizhong Tan, Shan Chen, Lei Wang, Yan Liu, Tai Io San, Sou Wang Fong, Wenyong Wang und Jing Feng. *Zeitschrift für Angewandte Wissenschaften*. Juni 2024. +- Noise Protocol Framework. https://noiseprotocol.org/ +- Vulnerability Management Framework-Projekt. https://phoenix.security/web-vuln-management/ + +--- + +🌟 Vielen Dank für Ihr Interesse an OpenNHP! Wir freuen uns auf Ihre Beiträge und Ihr Feedback. + diff --git a/README.es.md b/README.es.md index e69de29b..68fd9f45 100644 --- a/README.es.md +++ b/README.es.md @@ -0,0 +1,213 @@ +[![en](https://img.shields.io/badge/lang-en-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.md) +[![zh-cn](https://img.shields.io/badge/lang-zh--cn-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.zh-cn.md) +[![de](https://img.shields.io/badge/lang-de-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.de.md) +[![ja](https://img.shields.io/badge/lang-ja-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.ja.md) +[![fr](https://img.shields.io/badge/lang-fr-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.fr.md) +[![es](https://img.shields.io/badge/lang-es-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.es.md) + +![Logo OpenNHP](docs/images/logo11.png) +# OpenNHP: Protocolo de Ocultación de Infraestructura de Red Zero Trust +Un protocolo de red de confianza cero impulsado por criptografía en la capa 5 del modelo OSI para ocultar su servidor y sus datos de los atacantes. + +![Estado de Construcción](https://img.shields.io/badge/build-passing-brightgreen) +![Versión](https://img.shields.io/badge/version-1.0.0-blue) +![Licencia](https://img.shields.io/badge/license-Apache%202.0-green) + +--- + +## Desafío: La IA transforma Internet en un "Bosque Oscuro" + +El rápido avance de las tecnologías de **IA**, especialmente los grandes modelos de lenguaje (LLM), está transformando significativamente el panorama de la ciberseguridad. El surgimiento de la **Explotación Autónoma de Vulnerabilidades (AVE)** representa un gran avance en la era de la IA, al automatizar la explotación de vulnerabilidades, como se muestra en [este artículo de investigación](https://arxiv.org/abs/2404.08144). Este desarrollo aumenta significativamente el riesgo para todos los servicios de red expuestos, evocando la [Hipótesis del Bosque Oscuro](https://es.wikipedia.org/wiki/Hip%C3%B3tesis_del_bosque_oscuro) en Internet. Las herramientas impulsadas por IA escanean continuamente el entorno digital, identifican rápidamente las debilidades y las explotan. Como resultado, Internet está evolucionando hacia un **"bosque oscuro"** donde **la visibilidad equivale a vulnerabilidad**. + +![Riesgos de Vulnerabilidad](docs/images/Vul_Risks.png) + +La investigación de Gartner pronostica un [rápido aumento de los ciberataques impulsados por IA](https://www.gartner.com/en/newsroom/press-releases/2024-08-28-gartner-forecasts-global-information-security-spending-to-grow-15-percent-in-2025). Este cambio de paradigma requiere una reevaluación de las estrategias tradicionales de ciberseguridad, con un enfoque en defensas proactivas, mecanismos de respuesta rápida y la adopción de tecnologías de ocultación de red para proteger la infraestructura crítica. + +--- + +## Demostración rápida: Ver OpenNHP en acción + +Antes de profundizar en los detalles de OpenNHP, comencemos con una breve demostración de cómo OpenNHP protege un servidor del acceso no autorizado. Puede verlo en acción accediendo al servidor protegido en https://acdemo.opennhp.org. + +### 1) El servidor protegido es "invisible" para los usuarios no autenticados + +Por defecto, cualquier intento de conectar con el servidor protegido resultará en un error TIME OUT, ya que todos los puertos están cerrados, haciendo que el servidor parezca *"invisible"* y efectivamente fuera de línea. + +![Demostración de OpenNHP](docs/images/OpenNHP_ACDemo0.png) + +El escaneo de puertos del servidor también devolverá un error TIME OUT. + +![Demostración de OpenNHP](docs/images/OpenNHP_ScanDemo.png) + +### 2) Después de la autenticación, el servidor protegido se vuelve accesible + +OpenNHP admite una variedad de métodos de autenticación, como OAuth, SAML, códigos QR, y más. Para esta demostración, utilizamos un servicio de autenticación básica de nombre de usuario/contraseña en https://demologin.opennhp.org. + +![Demostración de OpenNHP](docs/images/OpenNHP_DemoLogin.png) + +Una vez que haga clic en el botón "Login", la autenticación se completará con éxito y será redirigido al servidor protegido. En ese momento, el servidor se vuelve *"visible"* y accesible en su dispositivo. + +![Demostración de OpenNHP](docs/images/OpenNHP_ACDemo1.png) + +--- + +## Visín: Hacer de Internet un lugar confiable + +La apertura de los protocolos TCP/IP ha impulsado el crecimiento explosivo de las aplicaciones de Internet, pero también ha expuesto vulnerabilidades, permitiendo que actores malintencionados obtengan acceso no autorizado y exploten cualquier dirección IP expuesta. Aunque el [modelo de red OSI](https://es.wikipedia.org/wiki/Modelo_OSI) define la *capa 5 (capa de sesión)* para la gestión de conexiones, pocas soluciones efectivas se han implementado para abordar este problema. + +**NHP**, o el **"Protocolo de Ocultación de la Infraestructura de Red"**, es un protocolo de red ligero y basado en criptografía Zero Trust, diseñado para funcionar en la *capa de sesión OSI*, óptimo para gestionar la visibilidad y las conexiones de la red. El objetivo principal de NHP es ocultar los recursos protegidos de entidades no autorizadas, otorgando acceso solo a los usuarios verificados y autorizados mediante una verificación continua, contribuyendo así a un Internet más confiable. + +![Internet confiable](docs/images/TrustworthyCyberspace.png) + +--- + +## Solución: OpenNHP restablece el control de la visibilidad de la red + +**OpenNHP** es la implementación de código abierto del protocolo NHP. Está impulsado por criptografía y diseñado con principios de seguridad en primer lugar, implementando una verdadera arquitectura de confianza cero en la *capa de sesión OSI*. + +![OpenNHP como la capa 5 del OSI](docs/images/OSI_OpenNHP.png) + +OpenNHP se basa en investigaciones anteriores sobre tecnología de ocultación de redes, utilizando un marco criptográfico moderno y una arquitectura que garantiza seguridad y alto rendimiento, superando las limitaciones de tecnologías anteriores. + +| Protocolo de Ocultación de Infraestructura de Red | 1ª Generación | 2ª Generación | 3ª Generación | +|:---|:---|:---|:---| +| **Tecnología Clave** | [Port Knocking](https://es.wikipedia.org/wiki/Port_knocking) | [Single Packet Authorization (SPA)](https://cloudsecurityalliance.org/artifacts/software-defined-perimeter-zero-trust-specification-v2) | Protocolo de Ocultación de Infraestructura de Red (NHP) | +| **Autenticación** | Secuencias de puertos | Secretos compartidos | Marco Criptográfico Moderno | +| **Arquitectura** | Sin plano de control | Sin plano de control | Plano de control escalable | +| **Capacidad** | Ocultar puertos | Ocultar puertos | Ocultar puertos, IPs y dominios | +| **Control de Acceso** | Nivel IP | Nivel de Puertos | Nivel de Aplicación | +| **Proyectos de Código Abierto** | [knock](https://github.com/jvinet/knock) *(C)* | [fwknop](https://github.com/mrash/fwknop) *(C++)* | [OpenNHP](https://github.com/OpenNHP/opennhp) *(Go)* | + +> Es crucial elegir un lenguaje **seguro para la memoria** como *Go* para el desarrollo de OpenNHP, como se destaca en el [informe técnico del gobierno de los EE.UU.](https://www.whitehouse.gov/wp-content/uploads/2024/02/Final-ONCD-Technical-Report.pdf). Para una comparación detallada entre **SPA y NHP**, consulte la [sección a continuación](#comparison-between-spa-and-nhp). + +## Beneficios de Seguridad + +Dado que OpenNHP implementa los principios de confianza cero en la *capa de sesión OSI*, ofrece beneficios significativos: + +- Reduce la superficie de ataque ocultando la infraestructura +- Evita el reconocimiento no autorizado de la red +- Mitiga la explotación de vulnerabilidades +- Previene ataques de phishing mediante DNS cifrado +- Protege contra ataques DDoS +- Permite el control de acceso granular +- Proporciona seguimiento de conexiones basado en identidad +- Atribución de ataques + +## Arquitectura + +La arquitectura de OpenNHP se inspira en el [estándar de Arquitectura de Confianza Cero del NIST](https://www.nist.gov/publications/zero-trust-architecture). Sigue un diseño modular con los tres componentes principales: **NHP-Server**, **NHP-AC** y **NHP-Agent**, como se ilustra en el siguiente diagrama. + +![Arquitectura de OpenNHP](docs/images/OpenNHP_Arch.png) + +> Consulte la [documentación de OpenNHP](https://opennhp.org/) para obtener información detallada sobre la arquitectura y el flujo de trabajo. + +## Centro: Algoritmos Criptográficos + +La criptografía es el centro de OpenNHP, proporcionando seguridad robusta, un excelente rendimiento y escalabilidad mediante el uso de algoritmos criptográficos de vanguardia. A continuación se muestran los principales algoritmos y marcos criptográficos utilizados por OpenNHP: + +- **[Criptografía de Curva Elíptica (ECC)](https://es.wikipedia.org/wiki/Criptograf%C3%ADa_de_curva_el%C3%ADptica)**: Utilizada para criptografía asimétrica eficiente. + +> En comparación con RSA, ECC ofrece una mayor eficiencia con una encriptación más fuerte en longitudes de clave más cortas, mejorando tanto la transmisión en la red como el rendimiento computacional. La tabla a continuación muestra las diferencias en la fortaleza de la seguridad, las longitudes de clave y la proporción de longitud de clave entre RSA y ECC, junto con sus respectivos períodos de validez. + +| Fortaleza de Seguridad (bits) | Longitud de Clave DSA/RSA (bits) | Longitud de Clave ECC (bits) | Relación: ECC vs. DSA/RSA | Validez | +|:----------------------------:|:-------------------------------:|:---------------------------:|:--------------------------:|:-------:| +| 80 | 1024 | 160-223 | 1:6 | Hasta 2010 | +| 112 | 2048 | 224-255 | 1:9 | Hasta 2030 | +| 128 | 3072 | 256-383 | 1:12 | Después de 2031 | +| 192 | 7680 | 384-511 | 1:20 | | +| 256 | 15360 | 512+ | 1:30 | | + +- **[Marco de Protocolo Noise](https://noiseprotocol.org/)**: Permite el intercambio seguro de claves, el cifrado/descifrado de mensajes y la autenticación mutua. + +> El Protocolo Noise se basa en el [acuerdo de clave Diffie-Hellman](https://es.wikipedia.org/wiki/Intercambio_de_claves_Diffie-Hellman) y proporciona soluciones criptográficas modernas como la autenticación mutua y opcional, el ocultamiento de identidad, la confidencialidad directa y el cifrado de ida y vuelta. Probado por su seguridad y rendimiento, ya es utilizado por aplicaciones populares como [WhatsApp](https://www.whatsapp.com/security/WhatsApp-Security-Whitepaper.pdf), [Slack](https://github.com/slackhq/nebula) y [WireGuard](https://www.wireguard.com/). + +- **[Criptografía Basada en Identidad (IBC)](https://es.wikipedia.org/wiki/Criptograf%C3%ADa_basada_en_la_identidad)**: Simplifica la distribución de claves a escala. + +> Una distribución eficiente de claves es esencial para implementar Zero Trust. OpenNHP admite tanto PKI como IBC. Mientras que PKI se ha utilizado ampliamente durante décadas, depende de Autoridades Certificadoras (CA) centralizadas para la verificación de identidad y la gestión de claves, lo que puede ser costoso y llevar tiempo. En contraste, IBC permite un enfoque descentralizado y autogestionado para la verificación de identidad y la gestión de claves, haciéndolo más rentable para el entorno Zero Trust de OpenNHP, donde miles de millones de dispositivos o servidores pueden necesitar protección e integración en tiempo real. + +- **[Criptografía sin Certificados (CL-PKC)](https://es.wikipedia.org/wiki/Criptograf%C3%ADa_sin_certificado)**: Algoritmo IBC recomendado + +> CL-PKC es un esquema que mejora la seguridad al evitar la custodia de claves y abordar las limitaciones de la Criptografía Basada en Identidad (IBC). En la mayoría de los sistemas IBC, la clave privada de un usuario es generada por un Centro de Generación de Claves (KGC), lo cual conlleva riesgos significativos. Un KGC comprometido puede llevar a la exposición de todas las claves privadas de los usuarios, requiriendo plena confianza en el KGC. CL-PKC mitiga este problema dividiendo el proceso de generación de claves, de modo que el KGC solo tiene conocimiento de una clave privada parcial. Como resultado, CL-PKC combina las fortalezas de PKI e IBC, ofreciendo una mayor seguridad sin los inconvenientes de la gestión centralizada de claves. + +Lectura adicional: + +> Consulte la [documentación de OpenNHP](https://opennhp.org/cryptography/) para una explicación detallada de los algoritmos criptográficos utilizados en OpenNHP. + +## Características Clave + +- Mitiga la explotación de vulnerabilidades mediante la aplicación de reglas "denegar todo" por defecto +- Previene ataques de phishing mediante la resolución DNS cifrada +- Protege contra ataques DDoS ocultando la infraestructura +- Permite la atribución de ataques mediante conexiones basadas en identidad +- Control de acceso predeterminado para todos los recursos protegidos +- Autenticación basada en identidad y dispositivos antes del acceso a la red +- Resolución DNS cifrada para prevenir secuestro de DNS +- Infraestructura distribuida para mitigar ataques DDoS +- Arquitectura escalable con componentes desacoplados +- Integración con sistemas de gestión de identidades y accesos existentes +- Compatible con varios modelos de despliegue (cliente a puerta de enlace, cliente a servidor, etc.) +- Seguridad criptográfica con algoritmos modernos (ECC, Noise Protocol, IBC) + +
+Haga clic para expandir los detalles de las características + +- **Control de acceso predeterminado**: Todos los recursos están ocultos por defecto, solo siendo accesibles tras la autenticación y autorización. +- **Autenticación basada en identidad y dispositivos**: Garantiza que solo los usuarios conocidos en dispositivos aprobados puedan acceder. +- **Resolución DNS cifrada**: Evita el secuestro de DNS y los ataques de phishing asociados. +- **Mitigación de DDoS**: El diseño de infraestructura distribuida ayuda a proteger contra los ataques de denegación de servicio distribuidos. +- **Arquitectura escalable**: Los componentes desacoplados permiten un despliegue y escalado flexibles. +- **Integración IAM**: Funciona con sus sistemas de gestión de identidades y accesos existentes. +- **Despliegue flexible**: Compatible con varios modelos, incluido cliente a puerta de enlace, cliente a servidor y más. +- **Criptografía robusta**: Utiliza algoritmos modernos como ECC, Noise Protocol e IBC para una seguridad robusta. +
+ +## Despliegue + +OpenNHP admite varios modelos de despliegue para adaptarse a diferentes casos de uso: + +- Cliente a puerta de enlace: Asegura el acceso a varios servidores detrás de una puerta de enlace +- Cliente a servidor: Asegura directamente servidores/aplicaciones individuales +- Servidor a servidor: Asegura la comunicación entre servicios backend +- Puerta de enlace a puerta de enlace: Asegura conexiones entre sitios + +> Consulte la [documentación de OpenNHP](https://opennhp.org/deploy/) para obtener instrucciones detalladas de despliegue. + +## Comparación entre SPA y NHP +El protocolo Single Packet Authorization (SPA) está incluido en la [especificación del Perímetro Definido por Software (SDP)](https://cloudsecurityalliance.org/artifacts/software-defined-perimeter-zero-trust-specification-v2) publicada por la [Cloud Security Alliance (CSA)](https://cloudsecurityalliance.org/). NHP mejora la seguridad, la fiabilidad, la escalabilidad y la extensibilidad mediante un marco criptográfico moderno y una arquitectura moderna, como se demuestra en el [artículo de investigación AHAC](https://www.mdpi.com/2076-3417/14/13/5593). + +| - | SPA | NHP | Ventajas de NHP | +|:---|:---|:---|:---| +| **Arquitectura** | El componente de descifrado de paquetes SPA y autenticación de usuario/dispositivo está acoplado con el componente de control de acceso a la red en el servidor SPA. | NHP-Server (el componente de descifrado de paquetes y autenticación de usuario/dispositivo) y NHP-AC (el componente de control de acceso) están desacoplados. NHP-Server se puede desplegar en hosts separados y admite la escalabilidad horizontal. | | +| **Comunicación** | Dirección única | Bidireccional | Mejor fiabilidad con la notificación de estado del control de acceso | +| **Marco criptográfico** | Secretos compartidos | PKI o IBC, Marco Noise | | +| **Capacidad de Ocultación de Infraestructura de Red** | Solo puertos de servidor | Dominios, IPs y puertos | Más poderoso contra varios ataques (p. ej., vulnerabilidades, secuestro de DNS y ataques DDoS) | +| **Extensibilidad** | Ninguna, solo para SDP | Todo uso | Compatible con cualquier escenario que necesite oscurecimiento del servicio | +| **Interoperabilidad** | No disponible | Personalizable | NHP puede integrarse sin problemas con protocolos existentes (p. ej., DNS, FIDO, etc.) | + +## Contribuir + +¡Damos la bienvenida a las contribuciones a OpenNHP! Consulte nuestras [Directrices de Contribución](CONTRIBUTING.md) para obtener más información sobre cómo participar. + +## Licencia + +OpenNHP se publica bajo la [Licencia Apache 2.0](LICENSE). + +## Contacto + +- Sitio web del proyecto: [https://github.com/OpenNHP/opennhp](https://github.com/OpenNHP/opennhp) +- Correo electrónico: [opennhp@gmail.com](mailto:opennhp@gmail.com) +- Canal de Slack: [Únase a nuestro Slack](https://slack.opennhp.org) + +Para obtener una documentación más detallada, visite nuestra [Documentación Oficial](https://opennhp.org). + +## Referencias + +- [Especificación del Perímetro Definido por Software (SDP) v2.0](https://cloudsecurityalliance.org/artifacts/software-defined-perimeter-zero-trust-specification-v2). Jason Garbis, Juanita Koilpillai, Junaid lslam, Bob Flores, Daniel Bailey, Benfeng Chen, Eitan Bremler, Michael Roza, Ahmed Refaey Hussein. [*Cloud Security Alliance (CSA)*](https://cloudsecurityalliance.org/). Marzo 2022. +- [AHAC: Marco Avanzado de Control de Acceso Oculto en Red](https://www.mdpi.com/2076-3417/14/13/5593). Mudi Xu, Benfeng Chen, Zhizhong Tan, Shan Chen, Lei Wang, Yan Liu, Tai Io San, Sou Wang Fong, Wenyong Wang y Jing Feng. *Revista de Ciencias Aplicadas*. Junio 2024. +- Noise Protocol Framework. https://noiseprotocol.org/ +- Proyecto de Marco de Gestión de Vulnerabilidades. https://phoenix.security/web-vuln-management/ + +--- + +🌟 ¡Gracias por su interés en OpenNHP! Esperamos sus contribuciones y comentarios. + diff --git a/README.fr.md b/README.fr.md index e69de29b..28149129 100644 --- a/README.fr.md +++ b/README.fr.md @@ -0,0 +1,213 @@ +[![en](https://img.shields.io/badge/lang-en-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.md) +[![zh-cn](https://img.shields.io/badge/lang-zh--cn-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.zh-cn.md) +[![de](https://img.shields.io/badge/lang-de-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.de.md) +[![ja](https://img.shields.io/badge/lang-ja-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.ja.md) +[![fr](https://img.shields.io/badge/lang-fr-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.fr.md) +[![es](https://img.shields.io/badge/lang-es-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.es.md) + +![Logo OpenNHP](docs/images/logo11.png) +# OpenNHP : Protocole de Masquage de l'Infrastructure Réseau en Zéro Confiance +Un protocole réseau de zéro confiance, basé sur la cryptographie, au niveau 5 du modèle OSI, permettant de cacher votre serveur et vos données des attaquants. + +![Statut de Construction](https://img.shields.io/badge/build-passing-brightgreen) +![Version](https://img.shields.io/badge/version-1.0.0-blue) +![Licence](https://img.shields.io/badge/license-Apache%202.0-green) + +--- + +## Défi : L'IA transforme Internet en une "Forêt Sombre" + +L'avancement rapide des technologies d'**IA**, en particulier les grands modèles de langage (LLM), transforme de manière significative le paysage de la cybersécurité. L'émergence de l'**exploitation autonome des vulnérabilités (AVE)** représente un bond majeur dans l'ère de l'IA, automatisant l'exploitation des vulnérabilités, comme le montre [cet article de recherche](https://arxiv.org/abs/2404.08144). Ce développement augmente de manière significative le risque pour tous les services réseau exposés, évoquant l'hypothèse de la [forêt sombre](https://fr.wikipedia.org/wiki/For%C3%AAt_sombre) sur Internet. Les outils pilotés par l'IA scannent continuellement l'environnement numérique, identifiant rapidement les faiblesses et les exploitant. Ainsi, Internet devient une **"forêt sombre"** où **la visibilité équivaut à la vulnérabilité**. + +![Risques de Vulnérabilité](docs/images/Vul_Risks.png) + +Selon les recherches de Gartner, les [cyberattaques pilotées par l'IA vont augmenter rapidement](https://www.gartner.com/en/newsroom/press-releases/2024-08-28-gartner-forecasts-global-information-security-spending-to-grow-15-percent-in-2025). Ce paradigme en évolution impose une réévaluation des stratégies de cybersécurité traditionnelles, avec un accent sur les défenses proactives, des mécanismes de réponse rapide, et l'adoption de technologies de masquage réseau pour protéger les infrastructures critiques. + +--- + +## Démo rapide : Voir OpenNHP en action + +Avant de plonger dans les détails d'OpenNHP, commençons par une démonstration rapide de la façon dont OpenNHP protège un serveur contre les accès non autorisés. Vous pouvez le voir en action en accédant au serveur protégé à l'adresse suivante : https://acdemo.opennhp.org. + +### 1) Le serveur protégé est "invisible" aux utilisateurs non authentifiés + +Par défaut, toute tentative de connexion au serveur protégé résultera en une erreur de TYPE OUT, car tous les ports sont fermés, rendant le serveur *"invisible"* et apparemment hors ligne. + +![Démo OpenNHP](docs/images/OpenNHP_ACDemo0.png) + +Le scan des ports du serveur retournera également une erreur de TYPE OUT. + +![Démo OpenNHP](docs/images/OpenNHP_ScanDemo.png) + +### 2) Après authentification, le serveur protégé devient accessible + +OpenNHP supporte une variété de méthodes d'authentification, telles que OAuth, SAML, QR codes, et plus encore. Pour cette démonstration, nous utilisons un service d'authentification basé sur un nom d'utilisateur/mot de passe simple à l'adresse https://demologin.opennhp.org. + +![Démo OpenNHP](docs/images/OpenNHP_DemoLogin.png) + +Une fois que vous cliquez sur le bouton "Login", l'authentification est réussie, et vous êtes redirigé vers le serveur protégé. Le serveur devient alors *"visible"* et accessible sur votre appareil. + +![Démo OpenNHP](docs/images/OpenNHP_ACDemo1.png) + +--- + +## Vision : Faire d'Internet un espace de confiance + +L'ouverture des protocoles TCP/IP a stimulé la croissance des applications Internet, mais a aussi exposé des vulnérabilités, permettant aux acteurs malveillants d'accéder de manière non autorisée à toute adresse IP exposée. Bien que le [modèle réseau OSI](https://fr.wikipedia.org/wiki/Mod%C3%A8le_OSI) définisse la *couche 5 (couche session)* pour la gestion des connexions, peu de solutions efficaces ont été mises en place à cet égard. + +**NHP**, ou **"Protocole de Masquage de l'Infrastructure Réseau"**, est un protocole réseau de zéro confiance, basé sur la cryptographie, conçu pour fonctionner au *niveau de la couche session OSI*, idéal pour gérer la visibilité réseau et les connexions. L'objectif principal de NHP est de dissimuler les ressources protégées des entités non autorisées, accordant l'accès uniquement aux utilisateurs vérifiés et autorisés par une vérification continue, contribuant ainsi à un Internet plus digne de confiance. + +![Internet de Confiance](docs/images/TrustworthyCyberspace.png) + +--- + +## Solution : OpenNHP rétablit le contrôle de la visibilité réseau + +**OpenNHP** est l'implémentation open source du protocole NHP. Il est basé sur la cryptographie et conçu avec des principes de sécurité en priorité, implémentant une véritable architecture de zéro confiance au *niveau de la couche session OSI*. + +![OpenNHP en tant que couche 5 OSI](docs/images/OSI_OpenNHP.png) + +OpenNHP s'appuie sur des recherches antérieures sur la technologie de masquage réseau, en utilisant des cadres et une architecture modernes de cryptographie pour garantir la sécurité et des performances élevées, surmontant ainsi les limitations des technologies précédentes. + +| Protocole de Masquage de l'Infrastructure | 1ère Génération | 2ème Génération | 3ème Génération | +|:---|:---|:---|:---| +| **Technologie Clé** | [Port Knocking](https://fr.wikipedia.org/wiki/Port_knocking) | [Autorisation par Paquet Unique (SPA)](https://cloudsecurityalliance.org/artifacts/software-defined-perimeter-zero-trust-specification-v2) | Protocole de Masquage de l'Infrastructure Réseau (NHP) | +| **Authentification** | Séquences de ports | Secrets partagés | Cadre cryptographique moderne | +| **Architecture** | Pas de plan de contrôle | Pas de plan de contrôle | Plan de contrôle scalable | +| **Capacité** | Masquer les ports | Masquer les ports | Masquer les ports, IPs et domaines | +| **Contrôle d'Accès** | Niveau IP | Niveau Port | Niveau Application | +| **Projets Open Source** | [knock](https://github.com/jvinet/knock) *(C)* | [fwknop](https://github.com/mrash/fwknop) *(C++)* | [OpenNHP](https://github.com/OpenNHP/opennhp) *(Go)* | + +> Il est crucial de choisir un langage **sûr pour la mémoire** comme *Go* pour le développement d'OpenNHP, comme le souligne le [rapport technique du gouvernement des États-Unis](https://www.whitehouse.gov/wp-content/uploads/2024/02/Final-ONCD-Technical-Report.pdf). Pour une comparaison détaillée entre **SPA et NHP**, référez-vous à la [section ci-dessous](#comparison-between-spa-and-nhp). + +## Bénéfices en matière de sécurité + +Puisqu'OpenNHP implémente les principes de zéro confiance au *niveau de la couche session OSI*, il offre des avantages significatifs : + +- Réduit la surface d'attaque en cachant l'infrastructure +- Empêche la reconnaissance réseau non autorisée +- Atténue l'exploitation des vulnérabilités +- Empêche le phishing via DNS chiffré +- Protège contre les attaques DDoS +- Permet un contrôle d'accès granulaire +- Fournit un suivi des connexions basé sur l'identité +- Attribution des attaques + +## Architecture + +L'architecture d'OpenNHP s'inspire de la [norme d'architecture Zero Trust du NIST](https://www.nist.gov/publications/zero-trust-architecture). Elle suit une conception modulaire avec trois composants principaux : **NHP-Server**, **NHP-AC** et **NHP-Agent**, comme illustré dans le diagramme ci-dessous. + +![Architecture OpenNHP](docs/images/OpenNHP_Arch.png) + +> Veuillez consulter la [documentation d'OpenNHP](https://opennhp.org/) pour des informations détaillées sur l'architecture et le flux de travail. + +## Cœur : Algorithmes Cryptographiques + +La cryptographie est au cœur d'OpenNHP, fournissant une sécurité robuste, d'excellentes performances et une bonne évolutivité en utilisant des algorithmes cryptographiques de pointe. Voici les principaux algorithmes et cadres cryptographiques employés par OpenNHP : + +- **[Cryptographie à Courbes Elliptiques (ECC)](https://fr.wikipedia.org/wiki/Cryptographie_sur_courbe_elliptique)** : Utilisée pour la cryptographie asymétrique efficace. + +> Comparée à RSA, l'ECC offre une efficacité supérieure avec un chiffrement plus fort à des longueurs de clé plus courtes, améliorant la transmission réseau et les performances de calcul. Le tableau ci-dessous montre les différences de force de sécurité, de longueurs de clé et du ratio entre RSA et ECC, ainsi que leurs périodes de validité respectives. + +| Force de Sécurité (bits) | Longueur de Clé DSA/RSA (bits) | Longueur de Clé ECC (bits) | Ratio : ECC vs DSA/RSA | Validité | +|:--------------------------:|:------------------------------:|:--------------------------:|:-----------------------:|:---------:| +| 80 | 1024 | 160-223 | 1:6 | Jusqu'en 2010 | +| 112 | 2048 | 224-255 | 1:9 | Jusqu'en 2030 | +| 128 | 3072 | 256-383 | 1:12 | Après 2031 | +| 192 | 7680 | 384-511 | 1:20 | | +| 256 | 15360 | 512+ | 1:30 | | + +- **[Cadre de Protocole Noise](https://noiseprotocol.org/)** : Permet l'échange de clés sécurisé, le chiffrement/déchiffrement des messages, et l'authentification mutuelle. + +> Le protocole Noise est basé sur l'[accord de clé Diffie-Hellman](https://fr.wikipedia.org/wiki/%C3%89change_de_cl%C3%A9_Diffie-Hellman) et offre des solutions cryptographiques modernes telles que l'authentification mutuelle et optionnelle, le masquage de l'identité, la sécurité persistante, et le chiffrement à tour de passezà-tour de zéro. Déjà prouvé pour sa sécurité et ses performances, il est utilisé par des applications populaires comme [WhatsApp](https://www.whatsapp.com/security/WhatsApp-Security-Whitepaper.pdf), [Slack](https://github.com/slackhq/nebula), et [WireGuard](https://www.wireguard.com/). + +- **[Cryptographie basée sur l'Identité (IBC)](https://fr.wikipedia.org/wiki/Cryptographie_bas%C3%A9e_sur_l%27identit%C3%A9)** : Simplifie la distribution des clés à grande échelle. + +> Une distribution efficace des clés est essentielle pour implémenter le Zéro Confiance. OpenNHP prend en charge à la fois PKI et IBC. Alors que PKI est utilisée depuis des décennies, elle dépend de Certificats d'Autorité centralisés (CA) pour la vérification de l'identité et la gestion des clés, ce qui peut être long et coûteux. En revanche, l'IBC permet une approche décentralisée et autonome de la vérification de l'identité et de la gestion des clés, la rendant plus rentable pour l'environnement Zero Trust d'OpenNHP, où des milliards d'appareils ou de serveurs peuvent avoir besoin de protection et d'intégration en temps réel. + +- **[Cryptographie à Clé Publique sans Certificat (CL-PKC)](https://fr.wikipedia.org/wiki/Cryptographie_sans_certificat)** : Algorithme IBC recommandé + +> CL-PKC est un schéma qui améliore la sécurité en évitant la garde des clés et en répondant aux limites de la cryptographie basée sur l'identité (IBC). Dans la plupart des systèmes IBC, la clé privée d'un utilisateur est générée par un Centre de Génération de Clés (KGC), ce qui introduit des risques importants. Un KGC compromis peut entraîner l'exposition des clés privées de tous les utilisateurs, nécessitant une confiance totale dans le KGC. CL-PKC atténue ce problème en divisant le processus de génération de clés, de sorte que le KGC n'a connaissance que d'une clé privée partielle. En conséquence, CL-PKC combine les forces du PKI et de l'IBC, offrant une sécurité renforcée sans les inconvénients de la gestion centralisée des clés. + +Pour en savoir plus : + +> Veuillez consulter la [documentation OpenNHP](https://opennhp.org/cryptography/) pour une explication détaillée des algorithmes cryptographiques utilisés dans OpenNHP. + +## Principales Fonctionnalités + +- Atténue l'exploitation des vulnérabilités en appliquant par défaut des règles "deny-all" +- Empêche les attaques de phishing via la résolution DNS chiffrée +- Protège contre les attaques DDoS en cachant l'infrastructure +- Permet l'attribution des attaques via des connexions basées sur l'identité +- Contrôle d'accès par défaut pour toutes les ressources protégées +- Authentification basée sur l'identité et les appareils avant l'accès au réseau +- Résolution DNS chiffrée pour empêcher le piratage DNS +- Infrastructure distribuée pour atténuer les attaques DDoS +- Architecture évolutive avec des composants découplés +- Intégration avec les systèmes existants de gestion des identités et des accès +- Prend en charge divers modèles de déploiement (client-à-passerelle, client-à-serveur, etc.) +- Sécurité cryptographique avec des algorithmes modernes (ECC, Noise Protocol, IBC) + +
+Cliquez pour développer les détails des fonctionnalités + +- **Contrôle d'accès par défaut** : Toutes les ressources sont cachées par défaut, ne devenant accessibles qu'après authentification et autorisation. +- **Authentification basée sur l'identité et les appareils** : Garantit que seuls les utilisateurs connus sur des appareils approuvés peuvent accéder. +- **Résolution DNS chiffrée** : Empêche le piratage DNS et les attaques de phishing associées. +- **Atténuation des DDoS** : Conception d'infrastructure distribuée aide à protéger contre les attaques par DDoS. +- **Architecture évolutive** : Les composants découplés permettent un déploiement et une évolution flexibles. +- **Intégration IAM** : Fonctionne avec vos systèmes de gestion des identités et des accès. +- **Déploiement flexible** : Prend en charge divers modèles, y compris client-à-passerelle, client-à-serveur, et plus encore. +- **Cryptographie forte** : Utilise des algorithmes modernes comme ECC, Noise Protocol, et IBC pour une sécurité robuste. +
+ +## Déploiement + +OpenNHP prend en charge plusieurs modèles de déploiement pour répondre à différents cas d'utilisation : + +- Client-à-Passerelle : Sécurise l'accès à plusieurs serveurs derrière une passerelle +- Client-à-Serveur : Sécurise directement des serveurs/applications individuels +- Serveur-à-Serveur : Sécurise la communication entre les services backend +- Passerelle-à-Passerelle : Sécurise les connexions site-à-site + +> Veuillez consulter la [documentation OpenNHP](https://opennhp.org/deploy/) pour des instructions de déploiement détaillées. + +## Comparaison entre SPA et NHP +Le protocole d'Autorisation par Paquet Unique (SPA) est inclus dans la [spécification du Périmètre Défini par Logiciel (SDP)](https://cloudsecurityalliance.org/artifacts/software-defined-perimeter-zero-trust-specification-v2) publiée par l'[Alliance pour la Sécurité Cloud (CSA)](https://cloudsecurityalliance.org/). NHP améliore la sécurité, la fiabilité, la scalabilité et l'extensibilité grâce à un cadre et une architecture de cryptographie modernes, comme démontré dans l'article de recherche [AHAC](https://www.mdpi.com/2076-3417/14/13/5593). + +| - | SPA | NHP | Avantages de NHP | +|:---|:---|:---|:---| +| **Architecture** | Le déchiffrement du paquet SPA et le composant d'authentification de l'utilisateur/appareil sont couplés au composant de contrôle d'accès réseau dans le serveur SPA. | NHP-Server (le composant de déchiffrement de paquet et d'authentification utilisateur/appareil) et NHP-AC (le composant de contrôle d'accès) sont découplés. NHP-Server peut être déployé sur des hôtes distincts et prend en charge la mise à l'échelle horizontale. | | +| **Communication** | Simple direction | Bidirectionnelle | Meilleure fiabilité avec la notification d'état du contrôle d'accès | +| **Cadre cryptographique** | Secrets partagés | PKI ou IBC, Cadre Noise | | +| **Capacité de Masquage de l'Infrastructure Réseau** | Uniquement les ports de serveur | Domaines, IP et ports | Plus puissant contre diverses attaques (e.g., vulnérabilités, piratage DNS, et attaques DDoS) | +| **Extensibilité** | Aucune, uniquement pour SDP | Tout usage | Prise en charge de tout scénario nécessitant un obscurcissement de service | +| **Interopérabilité** | Non disponible | Personnalisable | NHP peut s'intégrer de manière transparente avec les protocoles existants (e.g., DNS, FIDO, etc.) | + +## Contribuer + +Nous accueillons avec plaisir les contributions à OpenNHP ! Veuillez consulter nos [lignes directrices de contribution](CONTRIBUTING.md) pour plus d'informations sur la manière de participer. + +## Licence + +OpenNHP est publié sous la [licence Apache 2.0](LICENSE). + +## Contact + +- Site Web du Projet : [https://github.com/OpenNHP/opennhp](https://github.com/OpenNHP/opennhp) +- E-mail : [opennhp@gmail.com](mailto:opennhp@gmail.com) +- Canal Slack : [Rejoignez notre Slack](https://slack.opennhp.org) + +Pour plus de documentation détaillée, veuillez visiter notre [Documentation Officielle](https://opennhp.org). + +## Références + +- [Spécification du Périmètre Défini par Logiciel (SDP) v2.0](https://cloudsecurityalliance.org/artifacts/software-defined-perimeter-zero-trust-specification-v2). Jason Garbis, Juanita Koilpillai, Junaid lslam, Bob Flores, Daniel Bailey, Benfeng Chen, Eitan Bremler, Michael Roza, Ahmed Refaey Hussein. [*Cloud Security Alliance (CSA)*](https://cloudsecurityalliance.org/). Mar 2022. +- [AHAC : Cadre Avancé de Contrôle d'Accès Caché au Réseau](https://www.mdpi.com/2076-3417/14/13/5593). Mudi Xu, Benfeng Chen, Zhizhong Tan, Shan Chen, Lei Wang, Yan Liu, Tai Io San, Sou Wang Fong, Wenyong Wang, et Jing Feng. *Journal des Sciences Appliquées*. Juin 2024. +- Noise Protocol Framework. https://noiseprotocol.org/ +- Projet de Cadre de Gestion des Vulnérabilités. https://phoenix.security/web-vuln-management/ + +--- + +🌟 Merci pour votre intérêt dans OpenNHP ! Nous attendons vos contributions et vos commentaires avec impatience. + diff --git a/README.ja.md b/README.ja.md index e69de29b..100560a7 100644 --- a/README.ja.md +++ b/README.ja.md @@ -0,0 +1,148 @@ +[![en](https://img.shields.io/badge/lang-en-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.md) +[![zh-cn](https://img.shields.io/badge/lang-zh--cn-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.zh-cn.md) +[![de](https://img.shields.io/badge/lang-de-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.de.md) +[![ja](https://img.shields.io/badge/lang-ja-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.ja.md) +[![fr](https://img.shields.io/badge/lang-fr-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.fr.md) +[![es](https://img.shields.io/badge/lang-es-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.es.md) + +![OpenNHP Logo](docs/images/logo11.png) +# OpenNHP: ゼロトラストネットワークインフラストラクチャ隠蔽プロトコル +攻撃者からサーバーとデータを隠すためのOSI第5層に位置する、軽量の暗号化駆動型ゼロトラストネットワークプロトコルです。 + +![Build Status](https://img.shields.io/badge/build-passing-brightgreen) +![Version](https://img.shields.io/badge/version-1.0.0-blue) +![License](https://img.shields.io/badge/license-Apache%202.0-green) + +--- + +## セキュリティの利点 + +OpenNHPは*OSIセッション層*でゼロトラストの原則を実装しているため、次のような大きな利点があります。 + +- インフラの隠蔽による攻撃面の削減 +- 不正なネットワーク偵察の防止 +- 脆弱性の悪用を防ぐ +- 暗号化されたDNSによるフィッシング防止 +- DDoS攻撃に対する防御 +- 細粒度のアクセス制御を実現 +- アイデンティティベースの接続追跡 +- 攻撃の帰属 + +## アーキテクチャ + +OpenNHPのアーキテクチャは[NISTゼロトラストアーキテクチャ標準](https://www.nist.gov/publications/zero-trust-architecture)に触発されています。以下の図に示すように、3つの主要なコンポーネント(**NHP-Server**、**NHP-AC**、**NHP-Agent**)を持つモジュール設計に従います。 + +![OpenNHP architecture](docs/images/OpenNHP_Arch.png) + +> アーキテクチャとワークフローの詳細については、[OpenNHPドキュメント](https://opennhp.org/)を参照してください。 + +## コア: 暗号化アルゴリズム + +暗号化はOpenNHPの中心にあり、強力なセキュリティ、高いパフォーマンス、およびスケーラビリティを提供するために最新の暗号化アルゴリズムを利用しています。以下は、OpenNHPで使用されている主要な暗号化アルゴリズムとフレームワークです。 + +- **[楕円曲線暗号(ECC)](https://en.wikipedia.org/wiki/Elliptic-curve_cryptography)**:効率的な公開鍵暗号に使用されています。 + +> RSAと比較して、ECCは短い鍵長で強力な暗号化を提供し、ネットワーク伝送と計算パフォーマンスを向上させます。以下の表は、RSAとECCのセキュリティ強度、鍵長、および鍵長の比率の違いを示し、それぞれの有効期間を示しています。 + +| セキュリティ強度(ビット) | DSA/RSA鍵長(ビット) | ECC鍵長(ビット) | 比率:ECC対DSA/RSA | 有効期限 | +|:------------------------:|:-------------------------:|:---------------------:|:----------------------:|:--------:| +| 80 | 1024 | 160-223 | 1:6 | 2010年まで | +| 112 | 2048 | 224-255 | 1:9 | 2030年まで | +| 128 | 3072 | 256-383 | 1:12 | 2031年以降 | +| 192 | 7680 | 384-511 | 1:20 | | +| 256 | 15360 | 512+ | 1:30 | | + +- **[ノイズプロトコルフレームワーク](https://noiseprotocol.org/)**:安全な鍵交換、メッセージの暗号化/復号化、および相互認証を可能にします。 + +> ノイズプロトコルは[ディフィー・ヘルマン鍵共有](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange)に基づいており、相互およびオプションの認証、アイデンティティの隠蔽、前方秘匿性、ゼロラウンドトリップ暗号化などの最新の暗号化ソリューションを提供します。そのセキュリティとパフォーマンスは、[WhatsApp](https://www.whatsapp.com/security/WhatsApp-Security-Whitepaper.pdf)、[Slack](https://github.com/slackhq/nebula)、および[WireGuard](https://www.wireguard.com/)などの人気アプリケーションで既に証明されています。 + +- **[アイデンティティベース暗号(IBC)](https://en.wikipedia.org/wiki/Identity-based_cryptography)**:大規模な鍵配布を簡素化します。 + +> 効率的な鍵配布は、ゼロトラストの実装に不可欠です。OpenNHPはPKIとIBCの両方をサポートしています。PKIは数十年にわたって広く使用されてきましたが、アイデンティティの確認と鍵管理に中央集権的な認証局(CA)に依存しており、時間とコストがかかることがあります。一方、IBCは、アイデンティティの確認と鍵管理を分散型で自己管理可能な方法で行うことができ、リアルタイムで何十億ものデバイスやサーバーを保護し、オンボーディングする必要があるOpenNHPのゼロトラスト環境において、よりコスト効率的です。 + +- **[証明書レス公開鍵暗号(CL-PKC)](https://en.wikipedia.org/wiki/Certificateless_cryptography)**:推奨されるIBCアルゴリズム + +> CL-PKCは、鍵エスクローを回避し、アイデンティティベース暗号(IBC)の制限に対処することでセキュリティを強化するスキームです。ほとんどのIBCシステムでは、ユーザーの秘密鍵は鍵生成センター(KGC)によって生成され、これは重大なリスクをもたらします。KGCが侵害された場合、すべてのユーザーの秘密鍵が公開される可能性があり、KGCへの完全な信頼が必要です。CL-PKCは鍵生成プロセスを分割し、KGCは部分的な秘密鍵のみを知っているため、CL-PKCはPKIとIBCの両方の強みを組み合わせ、中央集権的な鍵管理の欠点なしに強力なセキュリティを提供します。 + +詳細について: + +> OpenNHPで使用されている暗号化アルゴリズムの詳細な説明については、[OpenNHPドキュメント](https://opennhp.org/cryptography/)を参照してください。 + +## 主な機能 + +- デフォルトで「すべて拒否」ルールを適用することにより、脆弱性の悪用を軽減 +- 暗号化されたDNS解決を通じてフィッシング攻撃を防止 +- インフラの隠蔽によるDDoS攻撃の防御 +- アイデンティティベースの接続による攻撃の帰属 +- 保護されたリソースに対するすべてのアクセスをデフォルトで拒否 +- ネットワークアクセス前にアイデンティティおよびデバイスベースの認証 +- DNSハイジャックを防止するための暗号化されたDNS解決 +- DDoS攻撃を緩和するための分散インフラ +- 分離されたコンポーネントによるスケーラブルなアーキテクチャ +- 既存のアイデンティティおよびアクセス管理システムとの統合 +- さまざまな展開モデルをサポート(クライアント対ゲートウェイ、クライアント対サーバーなど) +- 最新のアルゴリズム(ECC、ノイズプロトコル、IBC)を使用した暗号化によるセキュリティの確保 + +
+機能の詳細を表示 + +- **デフォルト拒否のアクセス制御**:すべてのリソースはデフォルトで隠蔽され、認証と認可が行われた後にのみアクセス可能になります。 +- **アイデンティティおよびデバイスベースの認証**:既知のユーザーと承認されたデバイスのみがアクセス可能です。 +- **暗号化されたDNS解決**:DNSハイジャックとそれに伴うフィッシング攻撃を防止します。 +- **DDoS緩和**:分散型インフラ設計により、分散型サービス拒否攻撃を防御します。 +- **スケーラブルなアーキテクチャ**:分離されたコンポーネントにより柔軟な展開とスケーリングが可能です。 +- **IAM統合**:既存のアイデンティティおよびアクセス管理システムと連携します。 +- **柔軟な展開**:クライアント対ゲートウェイ、クライアント対サーバーなど、さまざまなモデルをサポートします。 +- **強力な暗号化**:ECC、ノイズプロトコル、IBCなどの最新アルゴリズムを使用して強力なセキュリティを提供します。 +
+ +## 展開 + +OpenNHPは、さまざまなユースケースに合わせた複数の展開モデルをサポートしています。 + +- クライアント対ゲートウェイ:ゲートウェイの背後にある複数のサーバーへのアクセスを保護します +- クライアント対サーバー:個々のサーバー/アプリケーションを直接保護します +- サーバー対サーバー:バックエンドサービス間の通信を保護します +- ゲートウェイ対ゲートウェイ:サイト間接続を保護します + +> 詳細な展開手順については、[OpenNHPドキュメント](https://opennhp.org/deploy/)を参照してください。 + +## SPAとNHPの比較 +[クラウドセキュリティアライアンス(CSA)](https://cloudsecurityalliance.org/)がリリースした[ソフトウェア定義境界(SDP)仕様](https://cloudsecurityalliance.org/artifacts/software-defined-perimeter-zero-trust-specification-v2)には、シングルパケット認証(SPA)プロトコルが含まれています。NHPは、最新の暗号化フレームワークとアーキテクチャを通じてセキュリティ、信頼性、スケーラビリティ、拡張性を向上させ、[AHAC研究論文](https://www.mdpi.com/2076-3417/14/13/5593)で示されているように従来の技術の限界を克服しています。 + +| - | SPA |NHP | NHPの利点 | +|:---|:---|:---|:---| +| **アーキテクチャ** | SPAサーバーのパケット復号化およびユーザー/デバイス認証コンポーネントがネットワークアクセス制御コンポーネントと結合されています。 | NHP-Server(パケット復号化およびユーザー/デバイス認証コンポーネント)とNHP-AC(アクセス制御コンポーネント)が分離されています。NHP-Serverは別のホストに展開でき、水平スケーリングをサポートします。 | | +| **通信** | 単方向 | 双方向 | アクセス制御のステータス通知による信頼性の向上 | +| **暗号化フレームワーク** | 共有シークレット | PKIまたはIBC、ノイズフレームワーク || +| **ネットワークインフラストラクチャ隠蔽能力** | サーバーポートのみ | ドメイン、IP、ポート | 脆弱性、DNSハイジャック、DDoS攻撃など、さまざまな攻撃に対する強力な防御 | +| **拡張性** | なし、SDP専用 | 汎用 | あらゆるサービス暗黒化の必要があるシナリオに対応 | +| **相互運用性** | 利用不可 | カスタマイズ可能| NHPは既存のプロトコル(例:DNS、FIDOなど)とシームレスに統合可能 | + +## コントリビューション + +OpenNHPへの貢献を歓迎します!貢献方法の詳細については、[コントリビューションガイドライン](CONTRIBUTING.md)を参照してください。 + +## ライセンス + +OpenNHPは[Apache 2.0ライセンス](LICENSE)の下でリリースされています。 + +## 連絡先 + +- プロジェクトウェブサイト:[https://github.com/OpenNHP/opennhp](https://github.com/OpenNHP/opennhp) +- メール:[opennhp@gmail.com](mailto:opennhp@gmail.com) +- Slackチャンネル:[Slackに参加する](https://slack.opennhp.org) + +詳細なドキュメントについては、[公式ドキュメント](https://opennhp.org)をご覧ください。 + +## 参考文献 + +- [ソフトウェア定義境界(SDP)仕様 v2.0](https://cloudsecurityalliance.org/artifacts/software-defined-perimeter-zero-trust-specification-v2)。Jason Garbis、Juanita Koilpillai、Junaid Islam、Bob Flores、Daniel Bailey、Benfeng Chen、Eitan Bremler、Michael Roza、Ahmed Refaey Hussein。[*クラウドセキュリティアライアンス(CSA)*](https://cloudsecurityalliance.org/)。2022年3月。 +- [AHAC:高度なネットワーク隠蔽アクセス制御フレームワーク](https://www.mdpi.com/2076-3417/14/13/5593)。Mudi Xu、Benfeng Chen、Zhizhong Tan、Shan Chen、Lei Wang、Yan Liu、Tai Io San、Sou Wang Fong、Wenyong Wang、Jing Feng。*応用科学ジャーナル*。2024年6月。 +- ノイズプロトコルフレームワーク。https://noiseprotocol.org/ +- 脆弱性管理フレームワークプロジェクト。https://phoenix.security/web-vuln-management/ + +--- + +✨ OpenNHPにご関心をお寄せいただき、ありがとうございます!皆様の貢献とフィードバックをお待ちしております。 + diff --git a/README.md b/README.md index cc41f7e4..4d42e130 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ [![en](https://img.shields.io/badge/lang-en-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.md) [![zh-cn](https://img.shields.io/badge/lang-zh--cn-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.zh-cn.md) -[![de](https://img.shields.io/badge/lang-de-red.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.de.md) -[![ja](https://img.shields.io/badge/lang-ja-red.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.ja.md) -[![fr](https://img.shields.io/badge/lang-fr-red.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.fr.md) -[![es](https://img.shields.io/badge/lang-es-red.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.es.md) +[![de](https://img.shields.io/badge/lang-de-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.de.md) +[![ja](https://img.shields.io/badge/lang-ja-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.ja.md) +[![fr](https://img.shields.io/badge/lang-fr-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.fr.md) +[![es](https://img.shields.io/badge/lang-es-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.es.md) -![OpenNHP Logo](docs/images/logo1.png) +![OpenNHP Logo](docs/images/logo11.png) # OpenNHP: Zero Trust Network-infrastructure Hiding Protocol +A lightweight cryptography-driven zero trust networking protocol at the OSI 5th layer to hide your server and data from attackers. ![Build Status](https://img.shields.io/badge/build-passing-brightgreen) ![Version](https://img.shields.io/badge/version-1.0.0-blue) @@ -22,14 +23,43 @@ The rapid advancement of **AI** technologies, particularly large language models Gartner research predicts a [rapid increase in AI-driven cyberattacks](https://www.gartner.com/en/newsroom/press-releases/2024-08-28-gartner-forecasts-global-information-security-spending-to-grow-15-percent-in-2025). This shifting paradigm calls for a reevaluation of traditional cybersecurity strategies, with a focus on proactive defenses, rapid response mechanisms, and the adoption of network hiding technologies to safeguard critical infrastructure. +--- + +## Quick Demo: See OpenNHP in Action + +Before diving into the details of OpenNHP, let's start with a quick demonstration of how OpenNHP protects a server from unauthorized access. You can see it in action by accessing the protected server at https://acdemo.opennhp.org. + +### 1) The Protected Server is "Invisible" to Unauthenticated Users + +By default, any attempt to connect to the protected server will result in a TIME OUT error, as all ports are closed, making the server appear offline and effectively *"invisible."* + +![OpenNHP Demo](docs/images/OpenNHP_ACDemo0.png) + +Port scanning the server will also return a TIME OUT error. + +![OpenNHP Demo](docs/images/OpenNHP_ScanDemo.png) + +### 2) After Authentication, the Protected Server Becomes Accessible + +OpenNHP supports a variety of authentication methods, such as OAuth, SAML, QR codes, and more. For simplicity, this demo uses a basic username/password authentication service at https://demologin.opennhp.org to demonstrate the process. + +![OpenNHP Demo](docs/images/OpenNHP_DemoLogin.png) + +Once you click the "Login" button, authentication is completed successfully, and you are redirected to the protected server. At this point, the server becomes *"visible"* and accessible on your device. + +![OpenNHP Demo](docs/images/OpenNHP_ACDemo1.png) + +--- + ## Vision: Making the Internet Trustworthy The openness of TCP/IP protocols has driven the explosive growth of internet applications but also exposed vulnerabilities, allowing malicious actors to gain unauthorized access and exploit any exposed IP address. Although the [OSI network model](https://en.wikipedia.org/wiki/OSI_model) defines the *5th layer (Session Layer)* for managing connections, few effective solutions have been implemented to address this. -**NHP**, or the **"Network-infrastructure Hiding Protocol"**, is a Zero Trust communication protocol designed to function at the *OSI Session Layer*, which is optimal for managing network visibility and connections. NHP's key objective is to conceal protected resources from unauthorized entities, granting access only to verified, authorized users through continuous verification, contributing to a more trustworthy Internet. +**NHP**, or the **"Network-infrastructure Hiding Protocol"**, is a lightweight cryptography-driven Zero Trust networking protocol designed to function at the *OSI Session Layer*, which is optimal for managing network visibility and connections. NHP's key objective is to conceal protected resources from unauthorized entities, granting access only to verified, authorized users through continuous verification, contributing to a more trustworthy Internet. ![Trustworthy Internet](docs/images/TrustworthyCyberspace.png) +--- ## Solution: OpenNHP Fixes the Network Visibility Control @@ -71,13 +101,13 @@ The OpenNHP architecture is inspired by the [NIST Zero Trust Architecture standa > Please refer to the [OpenNHP Documentation](https://opennhp.org/) for detailed information about architecture and workflow. -## Cryptographic Algorithms +## Core: Cryptographic Algorithms Cryptography is at the heart of OpenNHP, providing robust security, excellent performance, and scalability by utilizing cutting-edge cryptographic algorithms. Below are the key cryptographic algorithms and frameworks employed by OpenNHP: - **[Elliptic Curve Cryptography (ECC)](https://en.wikipedia.org/wiki/Elliptic-curve_cryptography):** Used for efficient public key cryptography. -Compared to RSA, ECC offers superior efficiency with stronger encryption at shorter key lengths, improving both network transmission and computational performance. The table below highlights the differences in security strength, key lengths, and the key length ratio between RSA and ECC, along with their respective validity periods. +> Compared to RSA, ECC offers superior efficiency with stronger encryption at shorter key lengths, improving both network transmission and computational performance. The table below highlights the differences in security strength, key lengths, and the key length ratio between RSA and ECC, along with their respective validity periods. | Security Strength (bits) | DSA/RSA Key Length (bits) | ECC Key Length (bits) | Ratio: ECC vs. DSA/RSA | Validity | |:------------------------:|:-------------------------:|:---------------------:|:----------------------:|:--------:| @@ -89,16 +119,19 @@ Compared to RSA, ECC offers superior efficiency with stronger encryption at shor - **[Noise Protocol Framework](https://noiseprotocol.org/):** Enables secure key exchange, message encryption/decryption, and mutual authentication. -The Noise Protocol is built around the [Diffie-Hellman key agreement](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange) and provides modern cryptographic solutions like mutual and optional authentication, identity hiding, forward secrecy, and zero round-trip encryption. Proven for its security and performance, it is already used by popular applications like [WhatsApp](https://www.whatsapp.com/security/WhatsApp-Security-Whitepaper.pdf), [Slack](https://github.com/slackhq/nebula) and [WireGuard](https://www.wireguard.com/). +> The Noise Protocol is built around the [Diffie-Hellman key agreement](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange) and provides modern cryptographic solutions like mutual and optional authentication, identity hiding, forward secrecy, and zero round-trip encryption. Proven for its security and performance, it is already used by popular applications like [WhatsApp](https://www.whatsapp.com/security/WhatsApp-Security-Whitepaper.pdf), [Slack](https://github.com/slackhq/nebula) and [WireGuard](https://www.wireguard.com/). - **[Identity-Based Cryptography (IBC)](https://en.wikipedia.org/wiki/Identity-based_cryptography):** Simplifies key distribution at scale. -Efficient key distribution is essential for implementing Zero Trust. OpenNHP supports both PKI and IBC. While PKI has been widely used for decades, it depends on centralized Certificate Authorities (CA) for identity verification and key management, which can be time-consuming and costly. In contrast, IBC allows for a decentralized and self-governing approach to identity verification and key management, making it more cost-effective for OpenNHP's Zero Trust environment, where billions of devices or servers may need protection and onboarding in real-time. +> Efficient key distribution is essential for implementing Zero Trust. OpenNHP supports both PKI and IBC. While PKI has been widely used for decades, it depends on centralized Certificate Authorities (CA) for identity verification and key management, which can be time-consuming and costly. In contrast, IBC allows for a decentralized and self-governing approach to identity verification and key management, making it more cost-effective for OpenNHP's Zero Trust environment, where billions of devices or servers may need protection and onboarding in real-time. - **[Certificateless Public Key Cryptography (CL-PKC)](https://en.wikipedia.org/wiki/Certificateless_cryptography):** Recommended IBC algorithm -CL-PKC is a scheme that enhances security by avoiding key escrow and addressing the limitations of Identity-Based Cryptography (IBC). In most IBC systems, a user's private key is generated by a Key Generation Center (KGC), which introduces significant risks. A compromised KGC can lead to the exposure of all users' private keys, requiring full trust in the KGC. CL-PKC mitigates this issue by splitting the key generation process, so the KGC only has knowledge of a partial private key. As a result, CL-PKC combines the strengths of both PKI and IBC, offering stronger security without the drawbacks of centralized key management. +> CL-PKC is a scheme that enhances security by avoiding key escrow and addressing the limitations of Identity-Based Cryptography (IBC). In most IBC systems, a user's private key is generated by a Key Generation Center (KGC), which introduces significant risks. A compromised KGC can lead to the exposure of all users' private keys, requiring full trust in the KGC. CL-PKC mitigates this issue by splitting the key generation process, so the KGC only has knowledge of a partial private key. As a result, CL-PKC combines the strengths of both PKI and IBC, offering stronger security without the drawbacks of centralized key management. +Further reading: + +> Please refer to the [OpenNHP Documentation](https://opennhp.org/cryptography/) for detailed explanation of cryptographic algorithms used in OpenNHP. ## Key Features @@ -128,30 +161,6 @@ CL-PKC is a scheme that enhances security by avoiding key escrow and addressing - **Strong cryptography**: Utilizes modern algorithms like ECC, Noise Protocol, and IBC for robust security. -## Quick Demo - -This section provides a brief demonstration of how OpenNHP functions. The server protected by OpenNHP is https://acdemo.opennhp.org. Normally, port 443 would be open for HTTPS services, but with the *NHP-AC* component installed, all ports are closed by default, enforcing a Zero Trust "deny-all" policy. - -### 1) The Protected Server is "Invisible" to Unauthenticated Users - -By default, any attempt to connect to the protected server will result in a TIME OUT error, as all ports are closed, making the server appear offline and effectively *"invisible."* - -![OpenNHP Demo](docs/images/OpenNHP_ACDemo0.png) - -Port scanning the server will also return a TIME OUT error. - -![OpenNHP Demo](docs/images/OpenNHP_ScanDemo.png) - -### 2) After Authentication, the Protected Server Becomes Accessible - -OpenNHP supports a variety of authentication methods, such as OAuth, SAML, QR codes, and more. For simplicity, this demo uses a basic username/password authentication service at https://demologin.opennhp.org to demonstrate the process. - -![OpenNHP Demo](docs/images/OpenNHP_DemoLogin.png) - -Once you click the "Login" button, authentication is completed successfully, and you are redirected to the protected server. At this point, the server becomes *"visible"* and accessible on your device. - -![OpenNHP Demo](docs/images/OpenNHP_ACDemo1.png) - ## Deployment OpenNHP supports multiple deployment models to suit different use cases: @@ -189,6 +198,7 @@ OpenNHP is released under the [Apache 2.0 License](LICENSE). ## Contact - Project Website: [https://github.com/OpenNHP/opennhp](https://github.com/OpenNHP/opennhp) +- Email: [opennhp@gmail.com](mailto:opennhp@gmail.com) - Slack Channel: [Join our Slack](https://slack.opennhp.org) For more detailed documentation, please visit our [Official Documentation](https://opennhp.org). diff --git a/README.zh-cn.md b/README.zh-cn.md index 47fe0e1a..f2a10d65 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -1,161 +1,213 @@ -[![en](https://img.shields.io/badge/lang-en-red.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.md) - -# 零信任网络隐身协议 - -## 当今挑战:AI 将互联网变成一个“黑暗森林” -人工智能技术,尤其是大型语言模型(LLM)的快速发展正在深刻改变网络安全领域的格局。自主漏洞利用(AVE)的出现标志着人工智能时代的重大升级,它简化了漏洞利用的过程,正如本研究论文中所讨论的那样。这一发展使任何暴露的网络服务都面临着更高的风险,与互联网的 “黑暗森林假说 ”产生了共鸣。人工智能工具不断扫描数字环境,快速识别并利用弱点。因此,互联网演变成了 “黑暗森林”,可见性等同于脆弱性。 - -![OpenNHP Logo](docs/Vul_Risks.png) - -这种模式的转变要求我们重新评估传统的网络安全方法,强调主动防御、快速反应机制,并在可能的情况下采用网络隐藏技术来保护关键基础设施。 - -## 解决方案:零信任网络隐身协议 -NHP,或者 **网络隐身协议** 是一种零信任通信协议,在 OSI网络模型的会话层运行,是网络可见性和连接管理的最佳场所。它的主要目标是使受保护的资源不被未经授权的实体发现,同时通过持续验证只允许经过验证的授权主体访问。NHP 从云安全联盟(CSA)发布的软件定义边界(SDP)规范中的单包授权(SPA)协议中汲取灵感。除了 SPA 的功能外,NHP 还增强了安全性、可靠性、可扩展性和可扩展性。这里列出了 NHP 和 SPA 的详细比较。 - -![OpenNHP as the OSI 5th layer](docs/OSI_OpenNHP.png) - -**OpenNHP** 是用 *Golang* 开发的 NHP 协议开源实现。它的设计遵循安全第一的原则,在 OSI 网络模型的会话层(第 5 层)协议上实现了真正的零信任架构。由于会话层负责连接建立和对话控制,因此在会话层实施零信任具有显著的优势: -- **降低漏洞风险:** TCP/IP 协议的开放性导致了 “默认信任 ”连接模式,允许任何人与提供服务的服务器端口建立连接。攻击者利用这种开放性来攻击服务器漏洞。NHP 协议通过在服务器端默认执行 “拒绝所有 ”规则,只允许授权主机建立连接,从而实现了 “绝不信任,始终验证 ”的零信任原则。这能有效减少漏洞利用,尤其是零日漏洞利用。 -- **减少网络钓鱼攻击:** DNS 劫持是对互联网安全的严重威胁,被用于网络钓鱼、窃取敏感信息或传播恶意软件等恶意目的。NHP 协议可作为加密 DNS 解析服务来缓解这一问题。当客户端的 NHP-Agent 向控制器组件 NHP-Server 发送带有受保护资源标识符(如域名)的敲击请求时,如果 NHP-Agent 成功通过验证,NHP-Server 将返回受保护资源的 IP 地址和端口号。由于 NHP 通信是经过加密和相互验证的,因此可以有效降低 DNS 劫持的风险。 -- **减轻DDoS攻击:** 如上所述,客户端无法在未经验证的情况下获取受保护资源的 IP 地址和端口号。如果受保护的资源分布在多个地点,NHP 服务器可能会向不同的客户端返回不同的 IP 地址,从而大大增加了 DDoS 攻击的难度和实施成本。 攻击归属: TCP/IP 协议的连接模式是基于 IP 的。有了 NHP,连接模式就变成了基于身份(ID)。在建立连接之前,必须对连接发起者的身份进行验证,从而大大提高了攻击的可识别性和可追溯性。 - -## 安全优势 - -- 通过隐藏基础设施减少攻击面 -- 防止未经授权的网络侦察 -- 减少漏洞利用 -- 通过加密DNS阻止网络钓鱼 -- 防止DDoS攻击 -- 实现细粒度访问控制 -- 提供基于身份的连接跟踪 - -## 主要功能 - -- 默认执行 “全部拒绝 ”规则 -- 减少漏洞利用 -- 通过加密DNS解析防止网络钓鱼攻击 -- 通过隐藏基础设施防止DDoS攻击 -- 通过基于身份的连接实现攻击归因 -- 默认拒绝所有受保护资源的访问控制 -- 网络访问前基于身份和设备的身份验证 -- 加密 DNS 解析 -- 防止 DNS 劫持 -- 可缓解 DDoS 攻击的分布式基础设施 -- 具有解耦组件的可扩展架构 与现有身份和访问管理系统集成 -- 支持各种部署模式(客户端到网关、客户端到服务器等) -- 使用现代算法(ECC、噪声协议、IBC)确保加密安全 - -## 架构和工作流程 -OpenNHP 架构受 NIST 零信任架构标准的启发。它采用模块化设计,核心组件如下: - -![OpenNHP architecture](docs/OpenNHP_Arch.png) - -### OpenNHP核心组件: -#### NHP-Agent -NHP-Agent是一个客户端组件,用于启动通信和请求访问受保护的资源。它可以通过以下方式实现: -- 独立的客户端应用程序 -- 集成到现有应用程序中的SDK -- 浏览器插件 -- 移动应用程序 - -NHP-Agent负责: -- 生成并向NHP服务器发送敲击请求 -- 维护安全通信渠道 -- 处理身份验证流程 - -#### NHP服务器 -NHP服务器是中央控制器,功能: - - 处理和验证来自代理的验证请求 - - 与授权服务提供商互动,做出政策决定 - - 管理NHP-AC组件,允许/拒绝访问 - - 处理密钥管理和加密操作 - - NHP服务器可以部署在分布式或集群配置中,以实现高可用性和可扩展性。 - -#### NHP-AC - -NHP-AC (访问控制)组件执行受保护资源的访问策略,主要功能 : -- 执行默认的全部拒绝规则 -- 根据NHP服务器指令打开/关闭访问权限 -- 确保受保护资源的网络隐蔽性 -- 记录访问尝试 - -### 与OpenNHP交互的组件: -- **受保护资源:** 资源提供者负责保护这些资源,如API接口、应用服务器、网关、路由器、网络设备等。在SDP 方案中,受保护资源是 SDP 网关和控制器。 -- **授权服务提供商(ASP):** 该提供商负责验证访问策略,并提供受保护资源的实际访问地址。在 SDP 方案中,ASP 可以是SDP控制器。 - -### 工作流程 -1.'NHP-Agent' 向 'NHP-Server' 发送敲击请求 -2.'NHP-Server'验证请求并检索代理信息 -3.'NHP-Server'查询授权服务提供商 -4.如果获得授权,'NHP-Server'指示'NHP-AC'允许访问 -5.'NHP-AC' 打开连接并通知 'NHP-Server' -6.'NHP-Server'向'NHP-Agent'提供资源访问详情 -7.'NHP-Agent'现在可以访问受保护的资源 -8. 访问记录作为日记存储,用于审计 - -## 快速开始 -在几分钟内启动并运行 OpenNHP: -```bash -git clone https://github.com/opennhp/opennhp.git -cd opennhp -make -./nhp-server run -``` - -## 安装步骤: -1.去github clone到本地: -```bash -git clone https://github.com/opennhp/nhp.git -``` -2.进入文件夹 -``` -cd nhp -``` -3.build工程: -```bash -make -``` -4.安装(optional): -```bash -sudo make install -``` -注意:运行 `sudo make install` 需要root权限。运行此命令前,请确保您信任源代码。 - -## 部署模式 - -OpenNHP 支持多种部署模式,以适应不同的使用情况: - -- 客户端到网关:确保访问网关后面的多个服务器 -- 客户端到服务器:直接保护单个服务器/应用程序的安全 -- 服务器到服务器:确保后端服务之间的通信安全 -- 网关到网关:确保站点到站点连接的安全 - -## 加密基础 -OpenNHP 采用最先进的加密算法: -- 椭圆曲线加密算法(ECC):用于高效的公钥操作 -- 噪声协议框架:用于安全密钥交换和身份验证 -- 基于身份的加密算法(IBC):大规模简化密钥管理 - -## SPA与NHP的比较(todo) -NHP 利用现代加密算法和编程语言确保安全性和高性能,有效解决了 SPA 的局限性。 - - -## 贡献 -我们欢迎为 OpenNHP 投稿!有关如何参与的详细信息,请参阅我们的贡献指南。 - -## 许可证 -OpenNHP 根据 Apache 2.0 许可发布。 - -## 联系我们 -- 项目网站: https://github.com/OpenNHP/opennhp -- Slack频道:加入我们的Slack - -如需更详细的文档,请访问我们的官方文档。(https://docs.opennhp.org) - - -## 引用 - -- Software-Defined Perimeter (SDP) Specification v2.0. Jason Garbis, Juanita Koilpillai, Junaid lslam, Bob Flores, Daniel Bailey, Benfeng Chen, Eitan Bremler, Michael Roza, Ahmed Refaey Hussein. Cloud Security Alliance(CSA). Mar 2022. -- AHAC: Advanced Network-Hiding Access Control Framework. Mudi Xu, Benfeng Chen, Zhizhong Tan, Shan Chen, Lei Wang, Yan Liu, Tai Io San, Sou Wang Fong, Wenyong Wang, and Jing Feng. Applied Sciences Journal. June 2024. -- Vulnerability Management Framework project. https://phoenix.security/web-vuln-management/ \ No newline at end of file +[![en](https://img.shields.io/badge/lang-en-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.md) +[![zh-cn](https://img.shields.io/badge/lang-zh--cn-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.zh-cn.md) +[![de](https://img.shields.io/badge/lang-de-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.de.md) +[![ja](https://img.shields.io/badge/lang-ja-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.ja.md) +[![fr](https://img.shields.io/badge/lang-fr-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.fr.md) +[![es](https://img.shields.io/badge/lang-es-green.svg)](https://github.com/OpenNHP/opennhp/blob/master/README.es.md) + +![OpenNHP Logo](docs/images/logo11.png) +# OpenNHP: 零信任网络隐身协议 +OpenNHP是一个轻量级、基于加密算法的零信任网络协议,其工作在OSI网络模型第五层,用于隐藏您的服务器和数据,避免被攻击者发现和访问 + +![Build Status](https://img.shields.io/badge/build-passing-brightgreen) +![Version](https://img.shields.io/badge/version-1.0.0-blue) +![License](https://img.shields.io/badge/license-Apache%202.0-green) + +--- + +## 挑战:AI 将互联网变为“黑暗森林” + +**AI** 技术的快速发展,尤其是大语言模型(LLM),正在显著改变网络安全格局。**自主漏洞利用(AVE)** 的兴起是 AI 时代的一个重大飞跃,大大简化了漏洞的利用,这一点在[这篇研究论文](https://arxiv.org/abs/2404.08144)中有详细说明。这一发展显著增加了任何暴露网络服务的风险,与互联网的[黑暗森林假说](https://en.wikipedia.org/wiki/Dark_forest_hypothesis)不谋而合。AI 驱动的工具不断扫描数字环境,迅速识别和利用弱点。因此,互联网正逐渐成为一个**“黑暗森林”**,**可见性意味着脆弱性**。 + +![Vulnerability Risks](docs/images/Vul_Risks.png) + +Gartner 研究预测,[AI 驱动的网络攻击将迅速增加](https://www.gartner.com/en/newsroom/press-releases/2024-08-28-gartner-forecasts-global-information-security-spending-to-grow-15-percent-in-2025)。这一变化要求重新评估传统的网络安全策略,重点放在主动防御、快速响应机制和网络隐藏技术的采用,以保护关键基础设施。 + +--- + +## 快速演示:查看 OpenNHP 的工作原理 + +在深入了解 OpenNHP 的详细信息之前,让我们先来看一个 OpenNHP 如何保护服务器免受未经授权访问的演示。您可以通过访问 https://acdemo.opennhp.org 查看其实际效果。 + +### 1) 受保护的服务器对未经身份验证的用户“不可见” + +默认情况下,任何试图连接受保护服务器的操作都会导致超时错误,因为所有端口都是关闭的,使服务器看起来像是*“离线”*且实际上是“不可见”的。 + +![OpenNHP Demo](docs/images/OpenNHP_ACDemo0.png) + +对服务器进行端口扫描也会返回超时错误。 + +![OpenNHP Demo](docs/images/OpenNHP_ScanDemo.png) + +### 2) 身份验证后,受保护的服务器变得可访问 + +OpenNHP 支持多种身份验证方法,如 OAuth、SAML、二维码等。为了演示方便,本次演示使用 https://demologin.opennhp.org 上的基本用户名/密码身份验证服务来展示该过程。 + +![OpenNHP Demo](docs/images/OpenNHP_DemoLogin.png) + +点击“登录”按钮后,身份验证成功完成,您会被重定向到受保护的服务器。此时,服务器在您的设备上变得*“可见”*并且可以访问。 + +![OpenNHP Demo](docs/images/OpenNHP_ACDemo1.png) + +--- + +## 愿景:让互联网变得值得信赖 + +TCP/IP 协议的开放性推动了互联网应用的爆炸式增长,但也暴露了漏洞,使得恶意攻击者可以获得未经授权的访问并利用任何暴露的 IP 地址。尽管 [OSI 网络模型](https://en.wikipedia.org/wiki/OSI_model) 在*第五层(会话层)*定义了连接管理,但在实际中很少有有效的解决方案能够应对这一挑战。 + +**NHP**,即**“网络基础设施隐藏协议”**,是一种轻量级、基于加密的零信任网络协议,旨在工作于*OSI 会话层*,该层在管理网络可见性和连接方面是最佳选择。NHP 的主要目标是将受保护的资源隐藏于未授权的实体,只允许经过验证的用户通过持续认证访问,从而为更值得信赖的互联网作出贡献。 + +![Trustworthy Internet](docs/images/TrustworthyCyberspace.png) + +--- + +## 解决方案:OpenNHP 解决网络可见性控制问题 + +**OpenNHP** 是 NHP 协议的开源实现。它基于加密技术,采用安全优先的原则,在*OSI 会话层*实现了真正的零信任架构。 + +![OpenNHP as the OSI 5th layer](docs/images/OSI_OpenNHP.png) + +OpenNHP 构建在早期的网络隐藏技术研究基础之上,利用现代加密框架和架构确保安全性和高性能,从而克服了前代技术的局限性。 + +| 网络隐藏协议 | 第一代 | 第二代 | 第三代 | +|:---|:---|:---|:---| +| **核心技术** | [端口敲门](https://en.wikipedia.org/wiki/Port_knocking) | [单包认证(SPA)](https://cloudsecurityalliance.org/artifacts/software-defined-perimeter-zero-trust-specification-v2) | 网络基础设施隐藏协议(NHP) | +| **身份认证** | 端口序列 | 共享密钥 | 现代加密框架 | +| **架构** | 无控制平面 | 无控制平面 | 可扩展控制平面 | +| **功能** | 隐藏端口 | 隐藏端口 | 隐藏端口、IP 和域名 | +| **访问控制** | IP 层级 | 端口层级 | 应用层级 | +| **开源项目** | [knock](https://github.com/jvinet/knock) *(C)* | [fwknop](https://github.com/mrash/fwknop) *(C++)* | [OpenNHP](https://github.com/OpenNHP/opennhp) *(Go)* | + +> 开发 OpenNHP 选择使用**内存安全**的语言如 *Go*,这一点在[美国政府技术报告](https://www.whitehouse.gov/wp-content/uploads/2024/02/Final-ONCD-Technical-Report.pdf)中得到了强调。有关 **SPA 和 NHP** 之间详细的比较,请参见[下文](#comparison-between-spa-and-nhp)。 + +## 安全性优势 + +由于 OpenNHP 在 *OSI 会话层*实现了零信任原则,因此具有显著的优势: + +- 通过隐藏基础设施减少攻击面 +- 防止未经授权的网络侦察 +- 减少漏洞利用的可能性 +- 通过加密的 DNS 保护防止钓鱼 +- 抵御 DDoS 攻击 +- 提供细粒度的访问控制 +- 实现基于身份的连接追踪 +- 支持攻击溯源 + +## 架构 + +OpenNHP 的架构受 [NIST 零信任架构标准](https://www.nist.gov/publications/zero-trust-architecture) 启发,采用模块化设计,包含三个核心组件:**NHP-Server**、**NHP-AC** 和 **NHP-Agent**,如下图所示。 + +![OpenNHP architecture](docs/images/OpenNHP_Arch.png) + +> 有关架构和工作流程的详细信息,请参阅 [OpenNHP 文档](https://opennhp.org/)。 + +## 核心:加密算法 + +加密是 OpenNHP 的核心,提供强大的安全性、出色的性能和可扩展性,使用了先进的加密算法。以下是 OpenNHP 采用的关键加密算法和框架: + +- **[椭圆曲线密码学(ECC)](https://en.wikipedia.org/wiki/Elliptic-curve_cryptography)**:用于高效的公钥密码学。 + +> 与 RSA 相比,ECC 具有更高的效率,以较短的密钥长度提供更强的加密能力,从而提高网络传输和计算性能。下表显示了 RSA 和 ECC 在安全强度、密钥长度和密钥长度比率上的差异,以及其有效期。 + +| 安全强度(位) | DSA/RSA 密钥长度(位) | ECC 密钥长度(位) | 比率:ECC 与 DSA/RSA | 有效期 | +|:---------------:|:----------------------:|:-----------------:|:------------------:|:------:| +| 80 | 1024 | 160-223 | 1:6 | 到 2010 年 | +| 112 | 2048 | 224-255 | 1:9 | 到 2030 年 | +| 128 | 3072 | 256-383 | 1:12 | 2031 年后 | +| 192 | 7680 | 384-511 | 1:20 | | +| 256 | 15360 | 512+ | 1:30 | | + +- **[Noise 协议框架](https://noiseprotocol.org/)**:用于安全的密钥交换、消息加密/解密和相互身份认证。 + +> Noise 协议基于[Diffie-Hellman 密钥交换](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange),提供了现代加密解决方案,如相互和可选认证、身份隐藏、前向安全性和零轮次加密。它已被 WhatsApp、Slack 和 WireGuard 等应用广泛验证并使用,证明其安全性和性能。 + +- **[基于身份的加密(IBC)](https://en.wikipedia.org/wiki/Identity-based_cryptography)**:简化了大规模的密钥分发。 + +> 高效的密钥分发是实现零信任的关键。OpenNHP 支持 PKI 和 IBC。虽然 PKI 已经被广泛使用,但它依赖于集中式的证书颁发机构(CA)进行身份验证和密钥管理,这在时间和成本上较为昂贵。相比之下,IBC 允许在身份验证和密钥管理方面采用去中心化和自我管理的方法,使其在 OpenNHP 的零信任环境中更具成本效益,尤其是在需要实时保护和管理数十亿设备或服务器的情况下。 + +- **[无证书公钥加密(CL-PKC)](https://en.wikipedia.org/wiki/Certificateless_cryptography)**:推荐的 IBC 算法。 + +> CL-PKC 是一种通过避免密钥托管和解决基于身份的加密(IBC)局限性来增强安全性的方案。在大多数 IBC 系统中,用户的私钥由密钥生成中心(KGC)生成,这带来了显著的风险。如果 KGC 被攻破,所有用户的私钥都可能被泄露,这要求对 KGC 完全信任。CL-PKC 通过将密钥生成过程分离,使 KGC 仅了解部分私钥,从而避免这一问题。结果,CL-PKC 结合了 PKI 和 IBC 的优点,在不牺牲安全性的情况下提供更强的保护。 + +更多阅读: + +> 有关 OpenNHP 中使用的加密算法的详细说明,请参阅 [OpenNHP 文档](https://opennhp.org/cryptography/)。 + +## 主要特性 + +- 通过强制默认“全部拒绝”规则减少漏洞利用 +- 通过加密的 DNS 解决防止钓鱼攻击 +- 通过隐藏基础设施保护免受 DDoS 攻击 +- 通过身份追踪连接实现攻击溯源 +- 对所有受保护资源的默认拒绝访问控制 +- 在网络访问前进行基于身份和设备的身份认证 +- 加密的 DNS 解决防止 DNS 劫持 +- 分布式基础设施抵御 DDoS 攻击 +- 解耦组件实现可扩展架构 +- 与现有身份和访问管理系统集成 +- 支持多种部署模型(客户端到网关、客户端到服务器等) +- 使用现代算法(ECC、Noise 协议、IBC)进行加密确保安全性 + +
+点击展开特性详情 + +- **默认拒绝访问控制**:所有资源默认隐藏,只有通过身份验证和授权后才会变得可访问。 +- **基于身份和设备的身份验证**:确保只有已知用户在授权设备上可以访问。 +- **加密的 DNS 解决**:防止 DNS 劫持和相关的钓鱼攻击。 +- **DDoS 缓解**:分布式基础设施设计有助于抵御分布式拒绝服务攻击。 +- **可扩展架构**:解耦组件允许灵活部署和扩展。 +- **IAM 集成**:可以与现有身份和访问管理系统配合使用。 +- **灵活部署**:支持包括客户端到网关、客户端到服务器等多种模型。 +- **强大加密**:使用现代算法如 ECC、Noise 协议和 IBC 确保安全性。 +
+ +## 部署 + +OpenNHP 支持多种部署模型,以适应不同的使用场景: + +- 客户端到网关:保护网关后面的多个服务器的访问 +- 客户端到服务器:直接保护单个服务器/应用 +- 服务器到服务器:保护后端服务之间的通信 +- 网关到网关:保护站点到站点的连接 + +> 有关详细部署说明,请参阅 [OpenNHP 文档](https://opennhp.org/deploy/)。 + +## SPA 和 NHP 的比较 +单包认证(SPA)协议被包含在 [软件定义边界(SDP)规范](https://cloudsecurityalliance.org/artifacts/software-defined-perimeter-zero-trust-specification-v2) 中,由 [云安全联盟(CSA)](https://cloudsecurityalliance.org/) 发布。NHP 通过现代加密框架和架构在安全性、可靠性、可扩展性和可扩展性方面进行了改进,这一点在 [AHAC 研究论文](https://www.mdpi.com/2076-3417/14/13/5593) 中得到了验证。 + +| - | SPA | NHP | NHP 优势 | +|:---|:---|:---|:---| +| **架构** | SPA 服务器中的 SPA 数据包解密和用户/设备身份验证组件与网络访问控制组件是耦合的。 | NHP-Server(数据包解密和用户/设备身份验证组件)和 NHP-AC(访问控制组件)是解耦的。NHP-Server 可以部署在独立的主机上,并支持水平扩展。 | | +| **通信** | 单向 | 双向 | 更好的可靠性,访问控制状态通知 | +| **加密框架** | 共享密钥 | PKI 或 IBC,Noise 框架 | | +| **隐藏网络基础设施的能力** | 仅服务器端口 | 域名、IP 和端口 | 更强大,针对各种攻击(如漏洞利用、DNS 劫持和 DDoS 攻击) | +| **可扩展性** | 无,仅适用于 SDP | 通用 | 支持任何需要服务暗化的场景 | +| **互操作性** | 不支持 | 可定制 | NHP 可以无缝集成现有协议(如 DNS、FIDO 等) | + +## 贡献 + +我们欢迎对 OpenNHP 的贡献!有关如何参与的更多信息,请参阅我们的[贡献指南](CONTRIBUTING.md)。 + +## 许可协议 + +OpenNHP 遵循 [Apache 2.0 许可协议](LICENSE)。 + +## 联系方式 + +- 项目网站:[https://github.com/OpenNHP/opennhp](https://github.com/OpenNHP/opennhp) +- 电子邮件:[opennhp@gmail.com](mailto:opennhp@gmail.com) +- Slack 频道:[加入我们的 Slack](https://slack.opennhp.org) + +有关更详细的文档,请访问我们的[官方网站](https://opennhp.org)。 + +## 参考文献 + +- [软件定义边界(SDP)规范 v2.0](https://cloudsecurityalliance.org/artifacts/software-defined-perimeter-zero-trust-specification-v2)。Jason Garbis、Juanita Koilpillai、Junaid lslam、Bob Flores、Daniel Bailey、Benfeng Chen、Eitan Bremler、Michael Roza、Ahmed Refaey Hussein。[*云安全联盟(CSA)*](https://cloudsecurityalliance.org/)。2022 年 3 月。 +- [AHAC:高级网络隐藏访问控制框架](https://www.mdpi.com/2076-3417/14/13/5593)。Mudi Xu、Benfeng Chen、Zhizhong Tan、Shan Chen、Lei Wang、Yan Liu、Tai Io San、Sou Wang Fong、Wenyong Wang 和 Jing Feng。*应用科学杂志*。2024 年 6 月。 +- Noise 协议框架。https://noiseprotocol.org/ +- 漏洞管理框架项目。https://phoenix.security/web-vuln-management/ + +--- + +🌟 感谢您对 OpenNHP 的关注!我们期待您的贡献和反馈。 + diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..2c9c5218 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,12 @@ +# Security Policy + +## Reporting Security Issues + +The OpenNHP team and community take security bugs in OpenNHP seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. + +To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/opennhp/opennhp/security/advisories/new) tab. + +The OpenNHP team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. + +Report security bugs in third-party modules to the person or team maintaining the module. + diff --git a/ac/config.go b/ac/config.go index 7402f11a..7f6ecefe 100644 --- a/ac/config.go +++ b/ac/config.go @@ -1,156 +1,225 @@ -package ac - -import ( - "fmt" - "io" - "os" - "path/filepath" - - "github.com/OpenNHP/opennhp/core" - "github.com/OpenNHP/opennhp/log" - "github.com/OpenNHP/opennhp/utils" - - toml "github.com/pelletier/go-toml/v2" -) - -var ( - baseConfigWatch io.Closer - serverConfigWatch io.Closer - - errLoadConfig = fmt.Errorf("config load error") -) - -type Config struct { - PrivateKeyBase64 string `json:"privateKey"` - ACId string `json:"acId"` - DefaultIp string `json:"defaultIp"` - AuthServiceId string `json:"aspId"` - ResourceIds []string `json:"resIds"` - Servers []*core.UdpPeer `json:"servers"` - IpPassMode int `json:"ipPassMode"` // 0: pass the knock source IP, 1: use pre-access mode and release the access source IP - LogLevel int `json:"logLevel"` -} - -type Peers struct { - Servers []*core.UdpPeer -} - -func (d *UdpAC) loadBaseConfig() error { - // config.toml - fileName := filepath.Join(ExeDirPath, "etc", "config.toml") - if err := d.updateBaseConfig(fileName); err != nil { - // report base config error - return err - } - - baseConfigWatch = utils.WatchFile(fileName, func() { - log.Info("base config: %s has been updated", fileName) - d.updateBaseConfig(fileName) - }) - return nil -} - -func (d *UdpAC) loadPeers() error { - // server.toml - fileName := filepath.Join(ExeDirPath, "etc", "server.toml") - if err := d.updateServerPeers(fileName); err != nil { - // ignore error - _ = err - } - - serverConfigWatch = utils.WatchFile(fileName, func() { - log.Info("server peer config: %s has been updated", fileName) - d.updateServerPeers(fileName) - }) - - return nil -} - -func (d *UdpAC) updateBaseConfig(file string) (err error) { - utils.CatchPanicThenRun(func() { - err = errLoadConfig - }) - - content, err := os.ReadFile(file) - if err != nil { - log.Error("failed to read base config: %v", err) - } - - var conf Config - if err := toml.Unmarshal(content, &conf); err != nil { - log.Error("failed to unmarshal base config: %v", err) - } - - if d.config == nil { - d.config = &conf - d.log.SetLogLevel(conf.LogLevel) - return err - } - - // update - if d.config.LogLevel != conf.LogLevel { - log.Info("set base log level to %d", conf.LogLevel) - d.log.SetLogLevel(conf.LogLevel) - d.config.LogLevel = conf.LogLevel - } - - if d.config.DefaultIp != conf.DefaultIp { - log.Info("set default ip mode to %s", conf.DefaultIp) - d.config.DefaultIp = conf.DefaultIp - } - - if d.config.IpPassMode != conf.IpPassMode { - log.Info("set ip pass mode to %d", conf.IpPassMode) - d.config.IpPassMode = conf.IpPassMode - } - - return err -} - -func (d *UdpAC) updateServerPeers(file string) (err error) { - utils.CatchPanicThenRun(func() { - err = errLoadConfig - }) - - content, err := os.ReadFile(file) - if err != nil { - log.Error("failed to read server peer config: %v", err) - } - - // update - var peers Peers - serverPeerMap := make(map[string]*core.UdpPeer) - if err := toml.Unmarshal(content, &peers); err != nil { - log.Error("failed to unmarshal server peer config: %v", err) - } - for _, p := range peers.Servers { - p.Type = core.NHP_SERVER - d.device.AddPeer(p) - serverPeerMap[p.PublicKeyBase64()] = p - } - - // remove old peers from device - d.serverPeerMutex.Lock() - defer d.serverPeerMutex.Unlock() - for pubKey := range d.serverPeerMap { - if _, found := serverPeerMap[pubKey]; !found { - d.device.RemovePeer(pubKey) - } - } - d.serverPeerMap = serverPeerMap - - return err -} - -func (d *UdpAC) IpPassMode() int { - return d.config.IpPassMode -} - -func (d *UdpAC) StopConfigWatch() { - if baseConfigWatch != nil { - baseConfigWatch.Close() - } - if serverConfigWatch != nil { - serverConfigWatch.Close() - } -} +package ac + +import ( + "fmt" + "io" + "os" + "path/filepath" + + "github.com/OpenNHP/opennhp/core" + "github.com/OpenNHP/opennhp/log" + "github.com/OpenNHP/opennhp/utils" + + toml "github.com/pelletier/go-toml/v2" +) + +var ( + baseConfigWatch io.Closer + httpConfigWatch io.Closer + serverPeerWatch io.Closer + + errLoadConfig = fmt.Errorf("config load error") +) + +type Config struct { + PrivateKeyBase64 string `json:"privateKey"` + ACId string `json:"acId"` + DefaultIp string `json:"defaultIp"` + AuthServiceId string `json:"aspId"` + ResourceIds []string `json:"resIds"` + Servers []*core.UdpPeer `json:"servers"` + IpPassMode int `json:"ipPassMode"` // 0: pass the knock source IP, 1: use pre-access mode and release the access source IP + LogLevel int `json:"logLevel"` +} + +type HttpConfig struct { + EnableHttp bool + EnableTLS bool + HttpListenPort int + TLSCertFile string + TLSKeyFile string +} + +type Peers struct { + Servers []*core.UdpPeer +} + +func (a *UdpAC) loadBaseConfig() error { + // config.toml + fileName := filepath.Join(ExeDirPath, "etc", "config.toml") + if err := a.updateBaseConfig(fileName); err != nil { + // report base config error + return err + } + + baseConfigWatch = utils.WatchFile(fileName, func() { + log.Info("base config: %s has been updated", fileName) + a.updateBaseConfig(fileName) + }) + return nil +} + +func (a *UdpAC) loadHttpConfig() error { + // http.toml + fileName := filepath.Join(ExeDirPath, "etc", "http.toml") + if err := a.updateHttpConfig(fileName); err != nil { + // ignore error + _ = err + } + + httpConfigWatch = utils.WatchFile(fileName, func() { + log.Info("http config: %s has been updated", fileName) + a.updateHttpConfig(fileName) + }) + return nil +} + +func (a *UdpAC) loadPeers() error { + // server.toml + fileName := filepath.Join(ExeDirPath, "etc", "server.toml") + if err := a.updateServerPeers(fileName); err != nil { + // ignore error + _ = err + } + + serverPeerWatch = utils.WatchFile(fileName, func() { + log.Info("server peer config: %s has been updated", fileName) + a.updateServerPeers(fileName) + }) + + return nil +} + +func (a *UdpAC) updateBaseConfig(file string) (err error) { + utils.CatchPanicThenRun(func() { + err = errLoadConfig + }) + + content, err := os.ReadFile(file) + if err != nil { + log.Error("failed to read base config: %v", err) + } + + var conf Config + if err := toml.Unmarshal(content, &conf); err != nil { + log.Error("failed to unmarshal base config: %v", err) + } + + if a.config == nil { + a.config = &conf + a.log.SetLogLevel(conf.LogLevel) + return err + } + + // update + if a.config.LogLevel != conf.LogLevel { + log.Info("set base log level to %d", conf.LogLevel) + a.log.SetLogLevel(conf.LogLevel) + a.config.LogLevel = conf.LogLevel + } + + if a.config.DefaultIp != conf.DefaultIp { + log.Info("set default ip mode to %s", conf.DefaultIp) + a.config.DefaultIp = conf.DefaultIp + } + + if a.config.IpPassMode != conf.IpPassMode { + log.Info("set ip pass mode to %d", conf.IpPassMode) + a.config.IpPassMode = conf.IpPassMode + } + + return err +} + +func (a *UdpAC) updateHttpConfig(file string) (err error) { + utils.CatchPanicThenRun(func() { + err = errLoadConfig + }) + + content, err := os.ReadFile(file) + if err != nil { + log.Error("failed to read http config: %v", err) + } + + var httpConf HttpConfig + if err := toml.Unmarshal(content, &httpConf); err != nil { + log.Error("failed to unmarshal http config: %v", err) + } + + // update + if httpConf.EnableHttp { + // start http server + if a.httpServer == nil || !a.httpServer.IsRunning() { + if a.httpServer != nil { + // stop old http server + go a.httpServer.Stop() + } + hs := &HttpAC{} + a.httpServer = hs + err = hs.Start(a, &httpConf) + if err != nil { + return err + } + } + } else { + // stop http server + if a.httpServer != nil && a.httpServer.IsRunning() { + go a.httpServer.Stop() + a.httpServer = nil + } + } + + a.httpConfig = &httpConf + return err +} + +func (a *UdpAC) updateServerPeers(file string) (err error) { + utils.CatchPanicThenRun(func() { + err = errLoadConfig + }) + + content, err := os.ReadFile(file) + if err != nil { + log.Error("failed to read server peer config: %v", err) + } + + // update + var peers Peers + serverPeerMap := make(map[string]*core.UdpPeer) + if err := toml.Unmarshal(content, &peers); err != nil { + log.Error("failed to unmarshal server peer config: %v", err) + } + for _, p := range peers.Servers { + p.Type = core.NHP_SERVER + a.device.AddPeer(p) + serverPeerMap[p.PublicKeyBase64()] = p + } + + // remove old peers from device + a.serverPeerMutex.Lock() + defer a.serverPeerMutex.Unlock() + for pubKey := range a.serverPeerMap { + if _, found := serverPeerMap[pubKey]; !found { + a.device.RemovePeer(pubKey) + } + } + a.serverPeerMap = serverPeerMap + + return err +} + +func (a *UdpAC) IpPassMode() int { + return a.config.IpPassMode +} + +func (a *UdpAC) StopConfigWatch() { + if baseConfigWatch != nil { + baseConfigWatch.Close() + } + if httpConfigWatch != nil { + httpConfigWatch.Close() + } + if serverPeerWatch != nil { + serverPeerWatch.Close() + } +} diff --git a/ac/constants.go b/ac/constants.go index 500f3d6d..db52786e 100644 --- a/ac/constants.go +++ b/ac/constants.go @@ -10,7 +10,8 @@ const ( ServerKeepaliveInterval = 20 // seconds ServerDiscoveryRetryBeforeFail = 3 - TempPortOpenTime = 30 // + TokenStoreRefreshInterval = 10 + TempPortOpenTime = 30 // IPSET_DEFAULT_NAME = "defaultset" IPSET_DEFAULT_DOWN_NAME = "defaultset_down" diff --git a/ac/httpac.go b/ac/httpac.go new file mode 100644 index 00000000..efd9328c --- /dev/null +++ b/ac/httpac.go @@ -0,0 +1,215 @@ +package ac + +import ( + "context" + "encoding/base64" + "net" + "net/http" + "net/url" + "os" + "path/filepath" + "sync" + "sync/atomic" + "time" + + "github.com/OpenNHP/opennhp/common" + "github.com/OpenNHP/opennhp/log" + "github.com/gin-gonic/gin" +) + +type HttpAC struct { + id string + ua *UdpAC + httpServer *http.Server + ginEngine *gin.Engine + listenAddr *net.TCPAddr + + wg sync.WaitGroup + running atomic.Bool + + // signals + signals struct { + stop chan struct{} + } +} + +// Note HttpServer must be started after starting UdpAC, when log and config have been setup +func (hs *HttpAC) Start(uac *UdpAC, hc *HttpConfig) error { + hs.id = time.Now().Format("2006-01-02 15:04:05") + log.Info("==================================================") + log.Info("=== HttpServer (%s) started ===", hs.id) + log.Info("==================================================") + + hs.ua = uac + + port := hc.HttpListenPort + if hc.HttpListenPort == 0 { + port = 62206 + } + // only listen to localhost for security reason. + hs.listenAddr = &net.TCPAddr{ + IP: net.IPv4(127, 0, 0, 1), + Port: port, + } + + hs.signals.stop = make(chan struct{}) + + gin.SetMode(gin.ReleaseMode) + hs.ginEngine = gin.New() + hs.ginEngine.Use(gin.LoggerWithWriter(uac.log.Writer())) + hs.ginEngine.Use(gin.Recovery()) + + hs.initRouter() + + hs.httpServer = &http.Server{ + Addr: hs.listenAddr.String(), + Handler: hs.ginEngine, + ReadTimeout: 4500 * time.Millisecond, + WriteTimeout: 4000 * time.Millisecond, + IdleTimeout: 5000 * time.Millisecond, + } + + hs.wg.Add(1) + if hc.EnableTLS { + certFilePath := filepath.Join(ExeDirPath, hc.TLSCertFile) + keyFilePath := filepath.Join(ExeDirPath, hc.TLSKeyFile) + _, err1 := os.Stat(certFilePath) + _, err2 := os.Stat(keyFilePath) + if err1 == nil && err2 == nil { + go func() { + defer hs.wg.Done() + log.Info("Listening https on %s", hs.listenAddr.String()) + var err = hs.httpServer.ListenAndServeTLS(certFilePath, keyFilePath) + if err != nil && err != http.ErrServerClosed { + log.Error("https server close error: %v\n", err) + //panic(err) + } + }() + + return nil + } + } + + go func() { + defer hs.wg.Done() + log.Info("Listening http on %s", hs.listenAddr.String()) + var err = hs.httpServer.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + log.Error("http server close error: %v\n", err) + //panic(err) + } + }() + + hs.running.Store(true) + return nil +} + +// Stop stops the HttpServer by setting the running flag to false, +// closing the stop channel, shutting down the underlying http server, +// waiting for all goroutines to finish, and logging a message indicating +// that the HttpServer has been stopped. +func (hs *HttpAC) Stop() { + if !hs.running.Load() { + // already stopped, do nothing + return + } + + hs.running.Store(false) + close(hs.signals.stop) + ctx, cancel := context.WithTimeout(context.Background(), 5500*time.Millisecond) + hs.httpServer.Shutdown(ctx) + + hs.wg.Wait() + cancel() + cancel = nil + log.Info("==================================================") + log.Info("=== HttpServer (%s) stopped ===", hs.id) + log.Info("==================================================") +} + +func (hs *HttpAC) IsRunning() bool { + return hs.running.Load() +} + +// init gin engine. Must be called at initialization +func (ha *HttpAC) initRouter() { + g := ha.ginEngine + + refreshGrp := g.Group("refresh") + refreshGrp.GET("/:token", func(ctx *gin.Context) { + var err error + token := ctx.Param("token") + log.Info("get refresh request. aspId: %s, query: %v", token, ctx.Request.URL.RawQuery) + + if len(token) == 0 { + err = common.ErrUrlPathInvalid + log.Error("path error: %v", err) + ctx.String(http.StatusOK, "{\"errMsg\": \"path error: %v\"}", err) + return + } + + if token, err = url.QueryUnescape(token); err != nil { + err = common.ErrUrlPathInvalid + log.Error("token error: %v", err) + ctx.String(http.StatusOK, "{\"errMsg\": \"token error: %v\"}", err) + return + } + + req := &common.HttpRefreshRequest{ + Token: token, + SrcIp: ctx.Query("srcip"), + } + + ha.HandleHttpRefreshOperations(ctx, req) + }) +} + +func (ha *HttpAC) HandleHttpRefreshOperations(c *gin.Context, req *common.HttpRefreshRequest) { + if len(req.SrcIp) == 0 { + c.String(http.StatusOK, "{\"errMsg\": \"empty source ip\"}") + return + } + + netIp := net.ParseIP(req.SrcIp) + if netIp == nil { + c.String(http.StatusOK, "{\"errMsg\": \"invalid source ip\"}") + return + } + + buf, err := base64.StdEncoding.DecodeString(req.Token) + if err != nil || len(buf) != 32 { + c.String(http.StatusOK, "{\"errMsg\": \"invalid token\"}") + return + } + + entry := ha.ua.VerifyAccessToken(req.Token) + if entry == nil { + c.String(http.StatusOK, "{\"errMsg\": \"token verification failed\"}") + return + } + + var found bool + var newSrcAddr *common.NetAddress + for _, addr := range entry.SrcAddrs { + if addr.Ip == req.SrcIp { + found = true + break + } + } + if !found { + newSrcAddr = &common.NetAddress{ + Ip: req.SrcIp, + Port: entry.SrcAddrs[0].Port, + Protocol: entry.SrcAddrs[0].Protocol, + } + entry.SrcAddrs = append(entry.SrcAddrs, newSrcAddr) + } + + _, err = ha.ua.HandleAccessControl(entry.User, entry.SrcAddrs, entry.DstAddrs, entry.OpenTime, nil) + if err != nil { + c.String(http.StatusOK, "{\"errMsg\": \"%s\"}", err) + return + } + + c.JSON(http.StatusOK, entry) +} diff --git a/ac/main/etc/http.toml b/ac/main/etc/http.toml new file mode 100644 index 00000000..b47fdac1 --- /dev/null +++ b/ac/main/etc/http.toml @@ -0,0 +1,12 @@ +# http server config + +# EnableHttp: true: turn on http server, false: shutdown http server. +# EnableTLS: whether to use TLS certificates for hosting https server. +# TLSCertFile: certificate file path. +# TLSKeyFile: key file path. +# to update http changes, you need to restart the http server by changing "EnableHttp" to "false" and then switch it back to "true". +EnableHttp = true +EnableTLS = true +HttpListenPort = 62206 +TLSCertFile = "cert/cert.pem" +TLSKeyFile = "cert/cert.key" diff --git a/ac/main/etc/server.toml b/ac/main/etc/server.toml index 713a25a9..9072c990 100644 --- a/ac/main/etc/server.toml +++ b/ac/main/etc/server.toml @@ -7,8 +7,8 @@ # ExpireTime (epoch timestamp in seconds): peer key validation will fail when it expires. [[Servers]] Hostname = "" -Ip = "192.168.56.101" +Ip = "192.168.80.35" Port = 62206 PubKeyBase64 = "WqJxe+Z4+wLen3VRgZx6YnbjvJFmptz99zkONCt/7gc=" -ExpireTime = 1716345064 +ExpireTime = 1924991999 diff --git a/ac/msghandler.go b/ac/msghandler.go index beb970aa..b515cdeb 100644 --- a/ac/msghandler.go +++ b/ac/msghandler.go @@ -21,373 +21,400 @@ const ( PASS_PRE_ACCESS_IP ) -func (d *UdpAC) HandleACOperations(ppd *core.PacketParserData) (err error) { - defer d.wg.Done() - d.wg.Add(1) +func (a *UdpAC) HandleUdpACOperations(ppd *core.PacketParserData) (err error) { + defer a.wg.Done() - acId := d.config.ACId + acId := a.config.ACId dopMsg := &common.ServerACOpsMsg{} artMsg := &common.ACOpsResultMsg{} transactionId := ppd.SenderTrxId + err = json.Unmarshal(ppd.BodyMessage, dopMsg) + if err != nil { + log.Error("ac(%s#%d)[HandleUdpACOperations] failed to parse %s message: %v", acId, transactionId, core.HeaderTypeToString(ppd.HeaderType), err) + artMsg.ErrCode = common.ErrJsonParseFailed.ErrorCode() + artMsg.ErrMsg = err.Error() + return + } + + srcAddrs := dopMsg.SourceAddrs + dstAddrs := dopMsg.DestinationAddrs + openTimeSec := int(dopMsg.OpenTime) + agentUser := &common.AgentUser{ + UserId: dopMsg.UserId, + DeviceId: dopMsg.DeviceId, + OrganizationId: dopMsg.OrganizationId, + AuthServiceId: dopMsg.AuthServiceId, + } + artMsg, err = a.HandleAccessControl(agentUser, srcAddrs, dstAddrs, openTimeSec, artMsg) + if err != nil { + log.Error("ac(%s#%d)[HandleUdpACOperations] HandleAccessControl failed, err: %v", acId, transactionId, err) + } + + // generate ac token and save user and access information + entry := &AccessEntry{ + User: agentUser, + SrcAddrs: srcAddrs, + DstAddrs: dstAddrs, + OpenTime: openTimeSec, + } + artMsg.ACToken = a.GenerateAccessToken(entry) + //log.Info("generate knock token: %s", artMsg.ACToken) + + // send ac result + artBytes, _ := json.Marshal(artMsg) + md := &core.MsgData{ + HeaderType: core.NHP_ART, + TransactionId: transactionId, + Compress: true, + PrevParserData: ppd, + Message: artBytes, + } + //log.Info("ART result: %s", string(artBytes)) + + // forward to a specific transaction + transaction := ppd.ConnData.FindRemoteTransaction(transactionId) + if transaction == nil { + log.Error("ac(%s#%d)[HandleUdpACOperations] transaction is not available", acId, transactionId) + err = common.ErrTransactionIdNotFound + return err + } + + transaction.NextMsgCh <- md + + return err +} + +func (a *UdpAC) HandleAccessControl(au *common.AgentUser, srcAddrs []*common.NetAddress, dstAddrs []*common.NetAddress, openTimeSec int, artMsgIn *common.ACOpsResultMsg) (artMsg *common.ACOpsResultMsg, err error) { + if artMsgIn == nil { + artMsg = &common.ACOpsResultMsg{} + } else { + artMsg = artMsgIn + } + // process ac operation - func() { - err = json.Unmarshal(ppd.BodyMessage, dopMsg) - if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] failed to parse %s message: %v", acId, transactionId, core.HeaderTypeToString(ppd.HeaderType), err) - artMsg.ErrCode = common.ErrJsonParseFailed.ErrorCode() - artMsg.ErrMsg = err.Error() - return - } + tempOpenTimeSec := TempPortOpenTime + // 1 sec timeout means exit defaultset access, so exit tempset too + if openTimeSec == 1 { + tempOpenTimeSec = 1 + } - srcAddrs := dopMsg.SourceAddrs - dstAddrs := dopMsg.DestinationAddrs - openTimeSec := int(dopMsg.OpenTime) - tempOpenTimeSec := TempPortOpenTime - // 1 sec timeout means exit defaultset access, so exit tempset too - if openTimeSec == 1 { - tempOpenTimeSec = 1 - } + // check empty src address + if len(srcAddrs) == 0 || len(dstAddrs) == 0 { + log.Error("[HandleAccessControl] no source or destination address specified") + err = common.ErrACEmptyPassAddress + artMsg.ErrCode = common.ErrACEmptyPassAddress.ErrorCode() + artMsg.ErrMsg = err.Error() + return + } - // check empty src address - if len(srcAddrs) == 0 || len(dstAddrs) == 0 { - log.Error("ac(%s#%d)[HandleACOperations] no source or destination address specified", acId, transactionId) - err = common.ErrACEmptyPassAddress - artMsg.ErrCode = common.ErrACEmptyPassAddress.ErrorCode() - artMsg.ErrMsg = err.Error() - return - } + // ac ipset operations + if a.ipset == nil { + log.Error("[HandleAccessControl] ipset is nil") + err = common.ErrACIPSetNotFound + artMsg.ErrCode = common.ErrACIPSetNotFound.ErrorCode() + artMsg.ErrMsg = err.Error() + return + } - // ac ipset operations - if d.ipset == nil { - log.Error("ac(%s#%d)[HandleACOperations] ipset is nil", acId, transactionId) - err = common.ErrACIPSetNotFound - artMsg.ErrCode = common.ErrACIPSetNotFound.ErrorCode() - artMsg.ErrMsg = err.Error() - return + // use ac default ip to override empty destination ip + if len(a.config.DefaultIp) > 0 { + for _, addr := range dstAddrs { + if len(addr.Ip) == 0 { + addr.Ip = a.config.DefaultIp + } } + } - // use ac default ip to override empty destination ip - if len(d.config.DefaultIp) > 0 { - for _, addr := range dstAddrs { - if len(addr.Ip) == 0 { - addr.Ip = d.config.DefaultIp - } + switch a.IpPassMode() { + // pass the knock ip immediately + case PASS_KNOCK_IP: + for _, srcAddr := range srcAddrs { + var ipType utils.IPTYPE + var ipNet *net.IPNet + if strings.Contains(srcAddr.Ip, ":") { + ipType = utils.IPV6 + _, ipNet, _ = net.ParseCIDR(srcAddr.Ip + "/121") + } else { + ipType = utils.IPV4 + _, ipNet, _ = net.ParseCIDR(srcAddr.Ip + "/25") } - } + log.Debug("src ip is %s, net range is %s", srcAddr, ipNet.String()) + + for _, dstAddr := range dstAddrs { + // for tcp + if len(dstAddr.Protocol) == 0 || dstAddr.Protocol == "tcp" || dstAddr.Protocol == "any" { + ipHashStr := fmt.Sprintf("%s,%d,%s", srcAddr.Ip, dstAddr.Port, dstAddr.Ip) + if dstAddr.Port == 0 { + ipHashStr = fmt.Sprintf("%s,1-65535,%s", srcAddr.Ip, dstAddr.Ip) + } - switch d.IpPassMode() { - // pass the knock ip immediately - case PASS_KNOCK_IP: - for _, srcAddr := range srcAddrs { - var ipType utils.IPTYPE - var ipNet *net.IPNet - if strings.Contains(srcAddr.Ip, ":") { - ipType = utils.IPV6 - _, ipNet, _ = net.ParseCIDR(srcAddr.Ip + "/121") - } else { - ipType = utils.IPV4 - _, ipNet, _ = net.ParseCIDR(srcAddr.Ip + "/25") + _, err = a.ipset.Add(ipType, 1, openTimeSec, ipHashStr) + if err != nil { + log.Error("[HandleAccessControl] add ipset %s error: %v", ipHashStr, err) + err = common.ErrACIPSetOperationFailed + artMsg.ErrCode = common.ErrACIPSetOperationFailed.ErrorCode() + artMsg.ErrMsg = err.Error() + return + } } - log.Debug("src ip is %s, net range is %s", srcAddr, ipNet.String()) - for _, dstAddr := range dstAddrs { - // for tcp - if len(dstAddr.Protocol) == 0 || dstAddr.Protocol == "tcp" || dstAddr.Protocol == "any" { - ipHashStr := fmt.Sprintf("%s,%d,%s", srcAddr.Ip, dstAddr.Port, dstAddr.Ip) - if dstAddr.Port == 0 { - ipHashStr = fmt.Sprintf("%s,1-65535,%s", srcAddr.Ip, dstAddr.Ip) - } + // for udp + if len(dstAddr.Protocol) == 0 || dstAddr.Protocol == "udp" || dstAddr.Protocol == "any" { + ipHashStr := fmt.Sprintf("%s,udp:%d,%s", srcAddr.Ip, dstAddr.Port, dstAddr.Ip) + if dstAddr.Port == 0 { + ipHashStr = fmt.Sprintf("%s,udp:1-65535,%s", srcAddr.Ip, dstAddr.Ip) + } - _, err = d.ipset.Add(ipType, 1, openTimeSec, ipHashStr) - if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] add ipset %s error: %v", acId, transactionId, ipHashStr, err) - err = common.ErrACIPSetOperationFailed - artMsg.ErrCode = common.ErrACIPSetOperationFailed.ErrorCode() - artMsg.ErrMsg = err.Error() - return - } + _, err = a.ipset.Add(ipType, 1, openTimeSec, ipHashStr) + if err != nil { + log.Error("[HandleAccessControl] add ipset %s error: %v", ipHashStr, err) + err = common.ErrACIPSetOperationFailed + artMsg.ErrCode = common.ErrACIPSetOperationFailed.ErrorCode() + artMsg.ErrMsg = err.Error() + return } + } - // for udp - if len(dstAddr.Protocol) == 0 || dstAddr.Protocol == "udp" || dstAddr.Protocol == "any" { - ipHashStr := fmt.Sprintf("%s,udp:%d,%s", srcAddr.Ip, dstAddr.Port, dstAddr.Ip) - if dstAddr.Port == 0 { - ipHashStr = fmt.Sprintf("%s,udp:1-65535,%s", srcAddr.Ip, dstAddr.Ip) - } + // for icmp ping + if dstAddr.Port == 0 && (len(dstAddr.Protocol) == 0 || dstAddr.Protocol == "any") { + for _, dstAddr := range dstAddrs { + ipHashStr := fmt.Sprintf("%s,icmp:8/0,%s", srcAddr.Ip, dstAddr.Ip) - _, err = d.ipset.Add(ipType, 1, openTimeSec, ipHashStr) + _, err = a.ipset.Add(ipType, 1, openTimeSec, ipHashStr) if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] add ipset %s error: %v", acId, transactionId, ipHashStr, err) + log.Error("[HandleAccessControl] add ipset %s error: %v", ipHashStr, err) err = common.ErrACIPSetOperationFailed artMsg.ErrCode = common.ErrACIPSetOperationFailed.ErrorCode() artMsg.ErrMsg = err.Error() return } } + } - // for icmp ping - if dstAddr.Port == 0 && (len(dstAddr.Protocol) == 0 || dstAddr.Protocol == "any") { - for _, dstAddr := range dstAddrs { - ipHashStr := fmt.Sprintf("%s,icmp:8/0,%s", srcAddr.Ip, dstAddr.Ip) - - _, err = d.ipset.Add(ipType, 1, openTimeSec, ipHashStr) - if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] add ipset %s error: %v", acId, transactionId, ipHashStr, err) - err = common.ErrACIPSetOperationFailed - artMsg.ErrCode = common.ErrACIPSetOperationFailed.ErrorCode() - artMsg.ErrMsg = err.Error() - return - } + // add tempset + if ipNet != nil { + netStr := ipNet.String() + if len(dstAddr.Protocol) == 0 || dstAddr.Protocol == "tcp" || dstAddr.Protocol == "any" { + netHashStr := fmt.Sprintf("%s,%d", netStr, dstAddr.Port) + if dstAddr.Port == 0 { + netHashStr = fmt.Sprintf("%s,1-65535", netStr) } + _, err = a.ipset.Add(ipType, 4, tempOpenTimeSec, netHashStr) } - // add tempset - if ipNet != nil { - netStr := ipNet.String() - if len(dstAddr.Protocol) == 0 || dstAddr.Protocol == "tcp" || dstAddr.Protocol == "any" { - netHashStr := fmt.Sprintf("%s,%d", netStr, dstAddr.Port) - if dstAddr.Port == 0 { - netHashStr = fmt.Sprintf("%s,1-65535", netStr) - } - _, err = d.ipset.Add(ipType, 4, tempOpenTimeSec, netHashStr) - } - - if len(dstAddr.Protocol) == 0 || dstAddr.Protocol == "udp" || dstAddr.Protocol == "any" { - netHashStr := fmt.Sprintf("%s,udp:%d", netStr, dstAddr.Port) - if dstAddr.Port == 0 { - netHashStr = fmt.Sprintf("%s,udp:1-65535", netStr) - } - _, err = d.ipset.Add(ipType, 4, tempOpenTimeSec, netHashStr) + if len(dstAddr.Protocol) == 0 || dstAddr.Protocol == "udp" || dstAddr.Protocol == "any" { + netHashStr := fmt.Sprintf("%s,udp:%d", netStr, dstAddr.Port) + if dstAddr.Port == 0 { + netHashStr = fmt.Sprintf("%s,udp:1-65535", netStr) } + _, err = a.ipset.Add(ipType, 4, tempOpenTimeSec, netHashStr) + } - if dstAddr.Port == 0 && (len(dstAddr.Protocol) == 0 || dstAddr.Protocol == "any") { - netHashStr := fmt.Sprintf("%s,icmp:8/0", netStr) - _, err = d.ipset.Add(ipType, 4, tempOpenTimeSec, netHashStr) - } + if dstAddr.Port == 0 && (len(dstAddr.Protocol) == 0 || dstAddr.Protocol == "any") { + netHashStr := fmt.Sprintf("%s,icmp:8/0", netStr) + _, err = a.ipset.Add(ipType, 4, tempOpenTimeSec, netHashStr) } } } + } - // return temporary listened port(s) and nhp access token, then pass the real ip when agent sends access message - case PASS_PRE_ACCESS_IP: - fallthrough - default: - // ac open a temporary tcp or udp port for access - dstIp := net.ParseIP(dstAddrs[0].Ip) - if dstIp == nil { - log.Error("ac(%s#%d)[HandleACOperations] destination IP %s is invalid", acId, transactionId, dstAddrs[0].Ip) - err = common.ErrInvalidIpAddress - artMsg.ErrCode = common.ErrInvalidIpAddress.ErrorCode() - artMsg.ErrMsg = err.Error() - return - } - - var ipType utils.IPTYPE - var netStr string - var netStr1 string - var pickedPort int - var tcpListener *net.TCPListener - var udpListener *net.UDPConn - - if strings.Contains(dstAddrs[0].Ip, ":") { - ipType = utils.IPV6 - netStr = "0:0:0:0:0:0:0:0/0" - } else { - // since ipset does not allow full ip range 0.0.0.0/0, we use two ip ranges - ipType = utils.IPV4 - netStr = "0.0.0.0/1" - netStr1 = "128.0.0.0/1" - } - - // openning temp tcp access - tcpListener, err = net.ListenTCP("tcp", &net.TCPAddr{ - IP: dstIp, - Port: 0, // ephemeral port - }) + // return temporary listened port(s) and nhp access token, then pass the real ip when agent sends access message + case PASS_PRE_ACCESS_IP: + fallthrough + default: + // ac open a temporary tcp or udp port for access + dstIp := net.ParseIP(dstAddrs[0].Ip) + if dstIp == nil { + log.Error("[HandleAccessControl] destination IP %s is invalid", dstAddrs[0].Ip) + err = common.ErrInvalidIpAddress + artMsg.ErrCode = common.ErrInvalidIpAddress.ErrorCode() + artMsg.ErrMsg = err.Error() + return + } - if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] temporary tcp listening error: %v", acId, transactionId, err) - err = common.ErrACTempPortListenFailed - artMsg.ErrCode = common.ErrACTempPortListenFailed.ErrorCode() - artMsg.ErrMsg = err.Error() - return - } + var ipType utils.IPTYPE + var netStr string + var netStr1 string + var pickedPort int + var tcpListener *net.TCPListener + var udpListener *net.UDPConn - // retrieve local port - tladdr := tcpListener.Addr() - tlocalAddr, locErr := net.ResolveTCPAddr(tladdr.Network(), tladdr.String()) - if locErr != nil { - log.Error("ac(%s#%d)[HandleACOperations] resolve local TCPAddr error: %v", acId, transactionId, locErr) - err = common.ErrACResolveTempPortFailed - artMsg.ErrCode = common.ErrACResolveTempPortFailed.ErrorCode() - artMsg.ErrMsg = err.Error() - return - } + if strings.Contains(dstAddrs[0].Ip, ":") { + ipType = utils.IPV6 + netStr = "0:0:0:0:0:0:0:0/0" + } else { + // since ipset does not allow full ip range 0.0.0.0/0, we use two ip ranges + ipType = utils.IPV4 + netStr = "0.0.0.0/1" + netStr1 = "128.0.0.0/1" + } - log.Debug("open temporary tcp port %s", tlocalAddr.String()) - portHashStr := fmt.Sprintf("%s,%d", netStr, tlocalAddr.Port) - _, err = d.ipset.Add(ipType, 4, tempOpenTimeSec, portHashStr) - if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] add ipset %s error: %v", acId, transactionId, portHashStr, err) - err = common.ErrACIPSetOperationFailed - artMsg.ErrCode = common.ErrACIPSetOperationFailed.ErrorCode() - artMsg.ErrMsg = err.Error() - return - } - portHashStr = fmt.Sprintf("%s,%d", netStr1, tlocalAddr.Port) - _, err = d.ipset.Add(ipType, 4, tempOpenTimeSec, portHashStr) - if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] add ipset %s error: %v", acId, transactionId, portHashStr, err) - err = common.ErrACIPSetOperationFailed - artMsg.ErrCode = common.ErrACIPSetOperationFailed.ErrorCode() - artMsg.ErrMsg = err.Error() - return - } + // openning temp tcp access + tcpListener, err = net.ListenTCP("tcp", &net.TCPAddr{ + IP: dstIp, + Port: 0, // ephemeral port + }) - pickedPort = tlocalAddr.Port - log.Info("ac(%s#%d)[HandleACOperations] open temporary tcp port on %s", acId, transactionId, tladdr.String()) + if err != nil { + log.Error("[HandleAccessControl] temporary tcp listening error: %v", err) + err = common.ErrACTempPortListenFailed + artMsg.ErrCode = common.ErrACTempPortListenFailed.ErrorCode() + artMsg.ErrMsg = err.Error() + return + } - // for temp udp access - udpListener, err = net.ListenUDP("udp", &net.UDPAddr{ - IP: dstIp, - Port: pickedPort, // ephemeral port(0) or continue with previously picked tcp port - }) - if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] temporary udp listening error: %v", acId, transactionId, err) - err = common.ErrACTempPortListenFailed - artMsg.ErrCode = common.ErrACTempPortListenFailed.ErrorCode() - artMsg.ErrMsg = err.Error() - return - } + // retrieve local port + tladdr := tcpListener.Addr() + tlocalAddr, locErr := net.ResolveTCPAddr(tladdr.Network(), tladdr.String()) + if locErr != nil { + log.Error("[HandleAccessControl] resolve local TCPAddr error: %v", locErr) + err = common.ErrACResolveTempPortFailed + artMsg.ErrCode = common.ErrACResolveTempPortFailed.ErrorCode() + artMsg.ErrMsg = err.Error() + return + } - // retrieve local port - uladdr := udpListener.LocalAddr() - _, locErr = net.ResolveUDPAddr(uladdr.Network(), uladdr.String()) - if locErr != nil { - log.Error("ac(%s#%d)[HandleACOperations] resolve local UDPAddr error: %v", acId, transactionId, locErr) - err = common.ErrACResolveTempPortFailed - artMsg.ErrCode = common.ErrACResolveTempPortFailed.ErrorCode() - artMsg.ErrMsg = err.Error() - return - } + log.Debug("open temporary tcp port %s", tlocalAddr.String()) + portHashStr := fmt.Sprintf("%s,%d", netStr, tlocalAddr.Port) + _, err = a.ipset.Add(ipType, 4, tempOpenTimeSec, portHashStr) + if err != nil { + log.Error("[HandleAccessControl] add ipset %s error: %v", portHashStr, err) + err = common.ErrACIPSetOperationFailed + artMsg.ErrCode = common.ErrACIPSetOperationFailed.ErrorCode() + artMsg.ErrMsg = err.Error() + return + } + portHashStr = fmt.Sprintf("%s,%d", netStr1, tlocalAddr.Port) + _, err = a.ipset.Add(ipType, 4, tempOpenTimeSec, portHashStr) + if err != nil { + log.Error("[HandleAccessControl] add ipset %s error: %v", portHashStr, err) + err = common.ErrACIPSetOperationFailed + artMsg.ErrCode = common.ErrACIPSetOperationFailed.ErrorCode() + artMsg.ErrMsg = err.Error() + return + } - log.Debug("open temporary udp port %s", tlocalAddr.String()) - pickedPort = tlocalAddr.Port - portHashStr = fmt.Sprintf("%s,udp:%d", netStr, tlocalAddr.Port) - _, err = d.ipset.Add(ipType, 4, tempOpenTimeSec, portHashStr) - if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] add ipset %s error: %v", acId, transactionId, portHashStr, err) - err = common.ErrACIPSetOperationFailed - artMsg.ErrCode = common.ErrACIPSetOperationFailed.ErrorCode() - artMsg.ErrMsg = err.Error() - return - } - portHashStr = fmt.Sprintf("%s,udp:%d", netStr1, tlocalAddr.Port) - _, err = d.ipset.Add(ipType, 4, tempOpenTimeSec, portHashStr) - if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] add ipset %s error: %v", acId, transactionId, portHashStr, err) - err = common.ErrACIPSetOperationFailed - artMsg.ErrCode = common.ErrACIPSetOperationFailed.ErrorCode() - artMsg.ErrMsg = err.Error() - return - } + pickedPort = tlocalAddr.Port + log.Info("[HandleAccessControl] open temporary tcp port on %s", tladdr.String()) - log.Info("ac(%s#%d)[HandleACOperations] open temporary udp port on %s", acId, transactionId, tladdr.String()) + // for temp udp access + udpListener, err = net.ListenUDP("udp", &net.UDPAddr{ + IP: dstIp, + Port: pickedPort, // ephemeral port(0) or continue with previously picked tcp port + }) + if err != nil { + log.Error("[HandleAccessControl] temporary udp listening error: %v", err) + err = common.ErrACTempPortListenFailed + artMsg.ErrCode = common.ErrACTempPortListenFailed.ErrorCode() + artMsg.ErrMsg = err.Error() + return + } - agentUser := &AgentUser{ - UserId: dopMsg.UserId, - DeviceId: dopMsg.DeviceId, - OrganizationId: dopMsg.OrganizationId, - } + // retrieve local port + uladdr := udpListener.LocalAddr() + _, locErr = net.ResolveUDPAddr(uladdr.Network(), uladdr.String()) + if locErr != nil { + log.Error("[HandleAccessControl] resolve local UDPAddr error: %v", locErr) + err = common.ErrACResolveTempPortFailed + artMsg.ErrCode = common.ErrACResolveTempPortFailed.ErrorCode() + artMsg.ErrMsg = err.Error() + return + } - artMsg.PreAccessAction = &common.PreAccessInfo{ - AccessPort: strconv.Itoa(pickedPort), - ACPubKey: d.device.PublicKeyExBase64(), - ACToken: d.GenerateAccessToken(agentUser), - } + log.Debug("open temporary udp port %s", tlocalAddr.String()) + pickedPort = tlocalAddr.Port + portHashStr = fmt.Sprintf("%s,udp:%d", netStr, tlocalAddr.Port) + _, err = a.ipset.Add(ipType, 4, tempOpenTimeSec, portHashStr) + if err != nil { + log.Error("[HandleAccessControl] add ipset %s error: %v", portHashStr, err) + err = common.ErrACIPSetOperationFailed + artMsg.ErrCode = common.ErrACIPSetOperationFailed.ErrorCode() + artMsg.ErrMsg = err.Error() + return + } + portHashStr = fmt.Sprintf("%s,udp:%d", netStr1, tlocalAddr.Port) + _, err = a.ipset.Add(ipType, 4, tempOpenTimeSec, portHashStr) + if err != nil { + log.Error("[HandleAccessControl] add ipset %s error: %v", portHashStr, err) + err = common.ErrACIPSetOperationFailed + artMsg.ErrCode = common.ErrACIPSetOperationFailed.ErrorCode() + artMsg.ErrMsg = err.Error() + return + } - if tcpListener != nil { - d.wg.Add(1) - go d.tcpTempAccessHandler(transactionId, tcpListener, agentUser, tempOpenTimeSec, dopMsg) - } + log.Info("[HandleAccessControl] open temporary udp port on %s", tladdr.String()) - if udpListener != nil { - d.wg.Add(1) - go d.udpTempAccessHandler(transactionId, udpListener, agentUser, tempOpenTimeSec, dopMsg) - } + tempEntry := &AccessEntry{ + User: au, + SrcAddrs: srcAddrs, + DstAddrs: dstAddrs, + OpenTime: tempOpenTimeSec, + } + artMsg.PreAccessAction = &common.PreAccessInfo{ + AccessPort: strconv.Itoa(pickedPort), + ACPubKey: a.device.PublicKeyExBase64(), + ACToken: a.GenerateAccessToken(tempEntry), } - log.Info("ac(%s#%d)[HandleACOperations] succeed", acId, transactionId) - artMsg.ErrCode = common.ErrSuccess.ErrorCode() - artMsg.OpenTime = dopMsg.OpenTime - }() - - // send ac result - artBytes, _ := json.Marshal(artMsg) + if tcpListener != nil { + a.wg.Add(1) + go a.tcpTempAccessHandler(tcpListener, tempOpenTimeSec, dstAddrs, openTimeSec) + } - md := &core.MsgData{ - HeaderType: core.NHP_ART, - TransactionId: transactionId, - Compress: true, - PrevParserData: ppd, - Message: artBytes, + if udpListener != nil { + a.wg.Add(1) + go a.udpTempAccessHandler(udpListener, tempOpenTimeSec, dstAddrs, openTimeSec) + } } - // forward to a specific transaction - transaction := ppd.ConnData.FindRemoteTransaction(transactionId) - if transaction == nil { - log.Error("ac(%s#%d)[HandleACOperations] transaction is not available", acId, transactionId) - err = common.ErrTransactionIdNotFound - return err - } + log.Info("[HandleAccessControl] succeed") - transaction.NextMsgCh <- md + artMsg.ErrCode = common.ErrSuccess.ErrorCode() + artMsg.OpenTime = uint32(openTimeSec) - return nil + return } -func (d *UdpAC) tcpTempAccessHandler(transactionId uint64, listener *net.TCPListener, au *AgentUser, timeoutSec int, dopMsg *common.ServerACOpsMsg) { - defer d.wg.Done() - defer d.DeleteAccessToken(au) +func (a *UdpAC) tcpTempAccessHandler(listener *net.TCPListener, timeoutSec int, dstAddrs []*common.NetAddress, openTimeSec int) { + defer a.wg.Done() defer listener.Close() - acId := d.config.ACId // accept only the first incoming tcp connection startTime := time.Now() deadlineTime := startTime.Add(time.Duration(timeoutSec) * time.Second) localAddrStr := listener.Addr().String() err := listener.SetDeadline(deadlineTime) if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] temporary port on %s failed to set tcp listen timeout", acId, transactionId, localAddrStr) + log.Error("[tcpTempAccessHandler] temporary port on %s failed to set tcp listen timeout", localAddrStr) return } conn, err := listener.Accept() if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] temporary port on %s tcp listen timeout", acId, transactionId, localAddrStr) + log.Error("[tcpTempAccessHandler] temporary port on %s tcp listen timeout", localAddrStr) return } defer conn.Close() err = conn.SetDeadline(deadlineTime) if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] temporary port on %s failed to set tcp conn timeout", acId, transactionId, localAddrStr) + log.Error("[tcpTempAccessHandler] temporary port on %s failed to set tcp conn timeout", localAddrStr) return } remoteAddrStr := conn.RemoteAddr().String() - pkt := d.device.AllocatePoolPacket() - defer d.device.ReleasePoolPacket(pkt) + pkt := a.device.AllocatePoolPacket() + defer a.device.ReleasePoolPacket(pkt) // monitor stop signals and quit connection earlier ctx, ctxCancel := context.WithDeadline(context.Background(), deadlineTime) defer ctxCancel() - go d.tempConnTerminator(conn, ctx) + go a.tempConnTerminator(conn, ctx) // tcp recv common header first n, err := conn.Read(pkt.Buf[:core.HeaderCommonSize]) if err != nil || n < core.HeaderCommonSize { - log.Error("ac(%s#%d)[HandleACOperations] failed to receive tcp packet header from remote address %s (%v)", acId, transactionId, remoteAddrStr, err) + log.Error("[tcpTempAccessHandler] failed to receive tcp packet header from remote address %s (%v)", remoteAddrStr, err) return } @@ -395,7 +422,7 @@ func (d *UdpAC) tcpTempAccessHandler(transactionId uint64, listener *net.TCPList // check type and payload size msgType, msgSize := pkt.HeaderTypeAndSize() if msgType != core.NHP_ACC { - log.Error("ac(%s#%d)[HandleACOperations] message type is not %s, close connection", acId, transactionId, core.HeaderTypeToString(core.NHP_ACC)) + log.Error("[tcpTempAccessHandler] message type is not %s, close connection", core.HeaderTypeToString(core.NHP_ACC)) return } @@ -409,13 +436,13 @@ func (d *UdpAC) tcpTempAccessHandler(transactionId uint64, listener *net.TCPList remainingSize := packetSize - n n, err = conn.Read(pkt.Buf[n:packetSize]) if err != nil || n < remainingSize { - log.Error("ac(%s#%d)[HandleACOperations] failed to receive tcp message body from remote address %s (%v)", acId, transactionId, remoteAddrStr, err) + log.Error("[tcpTempAccessHandler] failed to receive tcp message body from remote address %s (%v)", remoteAddrStr, err) return } pkt.Content = pkt.Buf[:packetSize] - log.Trace("receive tcp access packet (%s -> %s): %+v", remoteAddrStr, localAddrStr, pkt.Content) - log.Info("ac(%s#%d)[HandleACOperations] receive tcp access message (%s -> %s)", acId, transactionId, remoteAddrStr, localAddrStr) + //log.Trace("[tcpTempAccessHandler]receive tcp access packet (%s -> %s): %+v", remoteAddrStr, localAddrStr, pkt.Content) + log.Info("[tcpTempAccessHandler] receive tcp access message (%s -> %s)", remoteAddrStr, localAddrStr) pd := &core.PacketData{ BasePacket: pkt, @@ -424,41 +451,33 @@ func (d *UdpAC) tcpTempAccessHandler(transactionId uint64, listener *net.TCPList DecryptedMsgCh: make(chan *core.PacketParserData), } - if !d.IsRunning() { - log.Error("ac(%s#%d)[HandleACOperations] PacketData channel closed or being closed, skip decrypting", acId, transactionId) + if !a.IsRunning() { + log.Error("[tcpTempAccessHandler] PacketData channel closed or being closed, skip decrypting") return } // start message decryption - d.device.RecvPacketToMsg(pd) + a.device.RecvPacketToMsg(pd) // waiting for message decryption accPpd := <-pd.DecryptedMsgCh close(pd.DecryptedMsgCh) if accPpd.Error != nil { - log.Error("ac(%s#%d)[HandleACOperations] failed to decrypt tcp access message: %v", acId, transactionId, accPpd.Error) + log.Error("[tcpTempAccessHandler] failed to decrypt tcp access message: %v", accPpd.Error) return } accMsg := &common.AgentAccessMsg{} err = json.Unmarshal(accPpd.BodyMessage, accMsg) if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] failed to parse %s message: %v", acId, transactionId, core.HeaderTypeToString(accPpd.HeaderType), err) + log.Error("[tcpTempAccessHandler] failed to parse %s message: %v", core.HeaderTypeToString(accPpd.HeaderType), err) return } - remoteAgentUser := &AgentUser{ - UserId: accMsg.UserId, - DeviceId: accMsg.DeviceId, - OrganizationId: accMsg.OrganizationId, - } - - if d.VerifyAccessToken(remoteAgentUser, accMsg.ACToken) { + if a.VerifyAccessToken(accMsg.ACToken) != nil { remoteAddr, _ := net.ResolveTCPAddr(conn.RemoteAddr().Network(), conn.RemoteAddr().String()) srcAddrIp := remoteAddr.IP.String() - dstAddrs := dopMsg.DestinationAddrs - openTimeSec := int(dopMsg.OpenTime) var ipType utils.IPTYPE if strings.Contains(dstAddrs[0].Ip, ":") { ipType = utils.IPV6 @@ -472,43 +491,41 @@ func (d *UdpAC) tcpTempAccessHandler(transactionId uint64, listener *net.TCPList ipHashStr = fmt.Sprintf("%s,1-65535,%s", srcAddrIp, dstAddr.Ip) } - _, err = d.ipset.Add(ipType, 1, openTimeSec, ipHashStr) + _, err = a.ipset.Add(ipType, 1, openTimeSec, ipHashStr) if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] add ipset %s error: %v", acId, transactionId, ipHashStr, err) + log.Error("[tcpTempAccessHandler] add ipset %s error: %v", ipHashStr, err) return } } } } -func (d *UdpAC) udpTempAccessHandler(transactionId uint64, conn *net.UDPConn, au *AgentUser, timeoutSec int, dopMsg *common.ServerACOpsMsg) { - defer d.wg.Done() - defer d.DeleteAccessToken(au) +func (a *UdpAC) udpTempAccessHandler(conn *net.UDPConn, timeoutSec int, dstAddrs []*common.NetAddress, openTimeSec int) { + defer a.wg.Done() defer conn.Close() - acId := d.config.ACId // listen to accept and handle only one incoming connection startTime := time.Now() deadlineTime := startTime.Add(time.Duration(timeoutSec) * time.Second) localAddrStr := conn.LocalAddr().String() err := conn.SetDeadline(deadlineTime) if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] temporary port on %s failed to set udp conn timeout", acId, transactionId, localAddrStr) + log.Error("[udpTempAccessHandler] temporary port on %s failed to set udp conn timeout", localAddrStr) return } - pkt := d.device.AllocatePoolPacket() - defer d.device.ReleasePoolPacket(pkt) + pkt := a.device.AllocatePoolPacket() + defer a.device.ReleasePoolPacket(pkt) // monitor stop signals and quit connection earlier ctx, ctxCancel := context.WithDeadline(context.Background(), deadlineTime) defer ctxCancel() - go d.tempConnTerminator(conn, ctx) + go a.tempConnTerminator(conn, ctx) // udp recv, blocking until packet arrives or deadline reaches n, remoteAddr, err := conn.ReadFromUDP(pkt.Buf[:]) if err != nil || n < core.HeaderCommonSize { - log.Error("ac(%s#%d)[HandleACOperations] failed to receive udp packet (%v)", acId, transactionId, err) + log.Error("[udpTempAccessHandler] failed to receive udp packet (%v)", err) return } @@ -518,7 +535,7 @@ func (d *UdpAC) udpTempAccessHandler(transactionId uint64, conn *net.UDPConn, au // check type and payload size msgType, msgSize := pkt.HeaderTypeAndSize() if msgType != core.NHP_ACC { - log.Error("ac(%s#%d)[HandleACOperations] message type is not %s, close connection", acId, transactionId, core.HeaderTypeToString(core.NHP_ACC)) + log.Error("[udpTempAccessHandler] message type is not %s, close connection", core.HeaderTypeToString(core.NHP_ACC)) return } @@ -531,12 +548,12 @@ func (d *UdpAC) udpTempAccessHandler(transactionId uint64, conn *net.UDPConn, au } if n != packetSize { - log.Error("ac(%s#%d)[HandleACOperations] udp packet size incorrect from remote address %s", acId, transactionId, remoteAddrStr) + log.Error("[udpTempAccessHandler] udp packet size incorrect from remote address %s", remoteAddrStr) return } log.Trace("receive udp access packet (%s -> %s): %+v", remoteAddrStr, localAddrStr, pkt.Content) - log.Info("ac(%s#%d)[HandleACOperations] receive udp access message (%s -> %s)", acId, transactionId, remoteAddrStr, localAddrStr) + log.Info("[udpTempAccessHandler] receive udp access message (%s -> %s)", remoteAddrStr, localAddrStr) pd := &core.PacketData{ BasePacket: pkt, @@ -545,40 +562,32 @@ func (d *UdpAC) udpTempAccessHandler(transactionId uint64, conn *net.UDPConn, au DecryptedMsgCh: make(chan *core.PacketParserData), } - if !d.IsRunning() { - log.Error("ac(%s#%d)[HandleACOperations] PacketData channel closed or being closed, skip decrypting", acId, transactionId) + if !a.IsRunning() { + log.Error("[udpTempAccessHandler] PacketData channel closed or being closed, skip decrypting") return } // start packet decryption - d.device.RecvPacketToMsg(pd) + a.device.RecvPacketToMsg(pd) // waiting for packet decryption accPpd := <-pd.DecryptedMsgCh close(pd.DecryptedMsgCh) if accPpd.Error != nil { - log.Error("ac(%s#%d)[HandleACOperations] failed to decrypt udp access message: %v", acId, transactionId, accPpd.Error) + log.Error("[udpTempAccessHandler] failed to decrypt udp access message: %v", accPpd.Error) return } accMsg := &common.AgentAccessMsg{} err = json.Unmarshal(accPpd.BodyMessage, accMsg) if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] failed to parse %s message: %v", acId, transactionId, core.HeaderTypeToString(accPpd.HeaderType), err) + log.Error("[udpTempAccessHandler] failed to parse %s message: %v", core.HeaderTypeToString(accPpd.HeaderType), err) return } - remoteAgentUser := &AgentUser{ - UserId: accMsg.UserId, - DeviceId: accMsg.DeviceId, - OrganizationId: accMsg.OrganizationId, - } - - if d.VerifyAccessToken(remoteAgentUser, accMsg.ACToken) { + if a.VerifyAccessToken(accMsg.ACToken) != nil { srcAddrIp := remoteAddr.IP.String() - dstAddrs := dopMsg.DestinationAddrs - openTimeSec := int(dopMsg.OpenTime) var ipType utils.IPTYPE if strings.Contains(dstAddrs[0].Ip, ":") { ipType = utils.IPV6 @@ -593,9 +602,9 @@ func (d *UdpAC) udpTempAccessHandler(transactionId uint64, conn *net.UDPConn, au ipHashStr = fmt.Sprintf("%s,udp:1-65535,%s", srcAddrIp, dstAddr.Ip) } - _, err = d.ipset.Add(ipType, 1, openTimeSec, ipHashStr) + _, err = a.ipset.Add(ipType, 1, openTimeSec, ipHashStr) if err != nil { - log.Error("ac(%s#%d)[HandleACOperations] add ipset %s error: %v", acId, transactionId, ipHashStr, err) + log.Error("[udpTempAccessHandler] add ipset %s error: %v", ipHashStr, err) return } } @@ -603,15 +612,15 @@ func (d *UdpAC) udpTempAccessHandler(transactionId uint64, conn *net.UDPConn, au // for ping if dstAddr.Port == 0 && (len(dstAddr.Protocol) == 0 || dstAddr.Protocol == "any") { ipHashStr := fmt.Sprintf("%s,icmp:8/0,%s", remoteAddr.IP.String(), dstAddr.Ip) - d.ipset.Add(ipType, 1, openTimeSec, ipHashStr) + a.ipset.Add(ipType, 1, openTimeSec, ipHashStr) } } } } -func (d *UdpAC) tempConnTerminator(conn net.Conn, ctx context.Context) { +func (a *UdpAC) tempConnTerminator(conn net.Conn, ctx context.Context) { select { - case <-d.signals.stop: + case <-a.signals.stop: conn.Close() return diff --git a/ac/tokenstore.go b/ac/tokenstore.go new file mode 100644 index 00000000..40cb40aa --- /dev/null +++ b/ac/tokenstore.go @@ -0,0 +1,99 @@ +package ac + +import ( + "encoding/base64" + "encoding/binary" + "time" + + "github.com/emmansun/gmsm/sm3" + + "github.com/OpenNHP/opennhp/common" + "github.com/OpenNHP/opennhp/log" +) + +type AccessEntry struct { + User *common.AgentUser + SrcAddrs []*common.NetAddress + DstAddrs []*common.NetAddress + OpenTime int + ExpireTime time.Time +} + +type TokenToAccessMap = map[string]*AccessEntry // access token mapped into user and access information +type TokenStore = map[string]TokenToAccessMap // upper layer of tokens, indexed by first two characters + +func (a *UdpAC) GenerateAccessToken(entry *AccessEntry) string { + var tsBytes [8]byte + currTime := time.Now().UnixNano() + + hash := sm3.New() + binary.BigEndian.PutUint64(tsBytes[:], uint64(currTime)) + au := entry.User + hash.Write([]byte(a.config.ACId + au.UserId + au.DeviceId + au.OrganizationId + au.AuthServiceId)) + hash.Write(tsBytes[:]) + token := base64.StdEncoding.EncodeToString(hash.Sum(nil)) + hash.Reset() + + a.tokenStoreMutex.Lock() + defer a.tokenStoreMutex.Unlock() + + entry.ExpireTime = time.Now().Add(time.Duration(entry.OpenTime+5) * time.Second) // keep token for additional 5 seconds in case a request is received late + tokenMap, found := a.tokenStore[token[0:1]] + if found { + tokenMap[token] = entry + } else { + tokenMap := make(TokenToAccessMap) + tokenMap[token] = entry + a.tokenStore[token[0:1]] = tokenMap + } + + return token +} + +func (a *UdpAC) VerifyAccessToken(token string) *AccessEntry { + a.tokenStoreMutex.Lock() + defer a.tokenStoreMutex.Unlock() + + tokenMap, found := a.tokenStore[token[0:1]] + if found { + entry, found := tokenMap[token] + if found { + return entry + } + } + + return nil +} + +func (a *UdpAC) tokenStoreRefreshRoutine() { + defer a.wg.Done() + defer log.Info("tokenStoreRefreshRoutine stopped") + + log.Info("tokenStoreRefreshRoutine started") + + for { + select { + case <-a.signals.stop: + return + + case <-time.After(TokenStoreRefreshInterval * time.Second): + func() { + a.tokenStoreMutex.Lock() + defer a.tokenStoreMutex.Unlock() + + now := time.Now() + for head, tokenMap := range a.tokenStore { + for token, entry := range tokenMap { + if now.After(entry.ExpireTime) { + log.Info("[TokenStore] token %s expired, remove", token) + delete(tokenMap, token) + } + } + if len(tokenMap) == 0 { + delete(a.tokenStore, head) + } + } + }() + } + } +} diff --git a/ac/udpac.go b/ac/udpac.go index 5ce5097c..d9e1e2a6 100644 --- a/ac/udpac.go +++ b/ac/udpac.go @@ -1,815 +1,743 @@ -package ac - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "hash" - "net" - "path/filepath" - "strconv" - "sync" - "sync/atomic" - "time" - - "github.com/OpenNHP/opennhp/common" - "github.com/OpenNHP/opennhp/core" - "github.com/OpenNHP/opennhp/log" - "github.com/OpenNHP/opennhp/utils" - "github.com/OpenNHP/opennhp/version" -) - -var ( - ExeDirPath string -) - -type AgentUser struct { - UserId string - DeviceId string - OrganizationId string - hash hash.Hash -} - -func (au *AgentUser) Hash() string { - au.hash = core.NewHash(core.HASH_SM3) - au.hash.Write([]byte(au.UserId)) - au.hash.Write([]byte(au.DeviceId)) - au.hash.Write([]byte(au.OrganizationId)) - // do not include Agent's PublicKey in calculating hash, because it may vary between Curve25519 and SM2 - sum := au.hash.Sum(nil) - return string(sum) -} - -type AgentUserCodeMap = map[string]*map[string]string // agent hash string first letter > agent hash string > token - -type UdpAC struct { - config *Config - iptables *utils.IPTables - ipset *utils.IPSet - - stats struct { - totalRecvBytes uint64 - totalSendBytes uint64 - } - - log *log.Logger - - remoteConnectionMutex sync.Mutex - remoteConnectionMap map[string]*UdpConn // indexed by remote UDP address - - serverPeerMutex sync.Mutex - serverPeerMap map[string]*core.UdpPeer // indexed by server's public key - - AgentUserTokenMutex sync.Mutex - agentUserCodeMap AgentUserCodeMap - - device *core.Device - wg sync.WaitGroup - running atomic.Bool - - signals struct { - stop chan struct{} - serverMapUpdated chan struct{} - } - - recvMsgCh <-chan *core.PacketParserData - sendMsgCh chan *core.MsgData -} - -type UdpConn struct { - ConnData *core.ConnectionData - netConn *net.UDPConn - connected atomic.Bool - externalAddr string -} - -func (c *UdpConn) Close() { - c.netConn.Close() - c.ConnData.Close() -} - -/* -dirPath: the path of app or shared library entry point -logLevel: 0: silent, 1: error, 2: info, 3: debug, 4: verbose -*/ -func (d *UdpAC) Start(dirPath string, logLevel int) (err error) { - common.ExeDirPath = dirPath - ExeDirPath = dirPath - // init logger - d.log = log.NewLogger("NHP-AC", logLevel, filepath.Join(ExeDirPath, "logs"), "ac") - log.SetGlobalLogger(d.log) - - log.Info("=========================================================") - log.Info("=== NHP-AC %s started ===", version.Version) - log.Info("=== REVISION %s ===", version.CommitId) - log.Info("=== RELEASE %s ===", version.BuildTime) - log.Info("=========================================================") - - // init config - err = d.loadBaseConfig() - if err != nil { - return err - } - - d.iptables, err = utils.NewIPTables() - if err != nil { - log.Error("iptables command not found") - return - } - - d.ipset, err = utils.NewIPSet(false) - if err != nil { - log.Error("ipset command not found") - return - } - - prk, err := base64.StdEncoding.DecodeString(d.config.PrivateKeyBase64) - if err != nil { - log.Error("private key parse error %v\n", err) - return fmt.Errorf("private key parse error %v", err) - } - - d.device = core.NewDevice(core.NHP_AC, prk, nil) - if d.device == nil { - log.Critical("failed to create device %v\n", err) - return fmt.Errorf("failed to create device %v", err) - } - - d.remoteConnectionMap = make(map[string]*UdpConn) - d.serverPeerMap = make(map[string]*core.UdpPeer) - d.agentUserCodeMap = make(AgentUserCodeMap) - - // load peers - d.loadPeers() - - d.signals.stop = make(chan struct{}) - d.signals.serverMapUpdated = make(chan struct{}, 1) - - d.recvMsgCh = d.device.DecryptedMsgQueue - d.sendMsgCh = make(chan *core.MsgData, core.SendQueueSize) - - // start device routines - d.device.Start() - - // start ac routines - d.wg.Add(3) - go d.sendMessageRoutine() - go d.recvMessageRoutine() - go d.maintainServerConnectionRoutine() - - d.running.Store(true) - return nil -} - -func (d *UdpAC) Stop() { - d.running.Store(false) - close(d.signals.stop) - - d.device.Stop() - d.StopConfigWatch() - d.wg.Wait() - close(d.sendMsgCh) - close(d.signals.serverMapUpdated) - - log.Info("==========================") - log.Info("=== NHP-AC stopped ===") - log.Info("==========================") - d.log.Close() -} - -func (d *UdpAC) IsRunning() bool { - return d.running.Load() -} - -func (d *UdpAC) newConnection(addr *net.UDPAddr) (conn *UdpConn) { - conn = &UdpConn{} - var err error - // unlike tcp, udp dial is fast (just socket bind), so no need to run in a thread - conn.netConn, err = net.DialUDP("udp", nil, addr) - if err != nil { - log.Error("could not connect to remote addr %s", addr.String()) - return nil - } - - // retrieve local port - laddr := conn.netConn.LocalAddr() - localAddr, err := net.ResolveUDPAddr(laddr.Network(), laddr.String()) - if err != nil { - log.Error("resolve local UDPAddr error %v\n", err) - return nil - } - - log.Info("Dial up new UDP connection from %s to %s", localAddr.String(), addr.String()) - - conn.ConnData = &core.ConnectionData{ - Device: d.device, - CookieStore: &core.CookieStore{}, - RemoteTransactionMap: make(map[uint64]*core.RemoteTransaction), - LocalAddr: localAddr, - RemoteAddr: addr, - TimeoutMs: DefaultConnectionTimeoutMs, - SendQueue: make(chan *core.Packet, PacketQueueSizePerConnection), - RecvQueue: make(chan *core.Packet, PacketQueueSizePerConnection), - BlockSignal: make(chan struct{}), - SetTimeoutSignal: make(chan struct{}), - StopSignal: make(chan struct{}), - } - - // start connection receive routine - conn.ConnData.Add(1) - go d.recvPacketRoutine(conn) - - return conn -} - -func (d *UdpAC) sendMessageRoutine() { - defer d.wg.Done() - defer log.Info("sendMessageRoutine stopped") - - log.Info("sendMessageRoutine started") - - for { - select { - case <-d.signals.stop: - return - - case md, ok := <-d.sendMsgCh: - if !ok { - return - } - if md == nil || md.RemoteAddr == nil { - log.Warning("Invalid initiator session starter") - continue - } - - addrStr := md.RemoteAddr.String() - - d.remoteConnectionMutex.Lock() - conn, found := d.remoteConnectionMap[addrStr] - d.remoteConnectionMutex.Unlock() - - if found { - md.ConnData = conn.ConnData - } else { - conn = d.newConnection(md.RemoteAddr) - if conn == nil { - log.Error("Failed to dial to remote address: %s", addrStr) - continue - } - - d.remoteConnectionMutex.Lock() - d.remoteConnectionMap[addrStr] = conn - d.remoteConnectionMutex.Unlock() - - md.ConnData = conn.ConnData - - // launch connection routine - d.wg.Add(1) - go d.connectionRoutine(conn) - } - - d.device.SendMsgToPacket(md) - } - } -} - -func (d *UdpAC) SendPacket(pkt *core.Packet, conn *UdpConn) (n int, err error) { - defer func() { - atomic.AddUint64(&d.stats.totalSendBytes, uint64(n)) - atomic.StoreInt64(&conn.ConnData.LastLocalSendTime, time.Now().UnixNano()) - - if !pkt.KeepAfterSend { - d.device.ReleasePoolPacket(pkt) - } - }() - - pktType := core.HeaderTypeToString(pkt.HeaderType) - //log.Debug("Send [%s] packet (%s -> %s): %+v", pktType, conn.ConnData.LocalAddr.String(), conn.ConnData.RemoteAddr.String(), pkt.Content) - log.Info("Send [%s] packet (%s -> %s), %d bytes", pktType, conn.ConnData.LocalAddr.String(), conn.ConnData.RemoteAddr.String(), len(pkt.Content)) - log.Evaluate("Send [%s] packet (%s -> %s, %d bytes)", pktType, conn.ConnData.LocalAddr.String(), conn.ConnData.RemoteAddr.String(), len(pkt.Content)) - return conn.netConn.Write(pkt.Content) -} - -func (d *UdpAC) recvPacketRoutine(conn *UdpConn) { - addrStr := conn.ConnData.RemoteAddr.String() - - defer conn.ConnData.Done() - defer log.Debug("recvPacketRoutine for %s stopped", addrStr) - - log.Debug("recvPacketRoutine for %s started", addrStr) - - for { - select { - case <-conn.ConnData.StopSignal: - return - - default: - } - - // udp recv, blocking until packet arrives or netConn.Close() - pkt := d.device.AllocatePoolPacket() - n, err := conn.netConn.Read(pkt.Buf[:]) - if err != nil { - d.device.ReleasePoolPacket(pkt) - if n == 0 { - // udp connection closed, it is not an error - return - } - log.Error("Failed to receive from remote address %s (%v)", addrStr, err) - continue - } - - // add total recv bytes - atomic.AddUint64(&d.stats.totalRecvBytes, uint64(n)) - - // check minimal length - if n < core.HeaderSize { - d.device.ReleasePoolPacket(pkt) - log.Error("Received UDP packet from %s is too short, discard", addrStr) - continue - } - - pkt.Content = pkt.Buf[:n] - //log.Trace("receive udp packet (%s -> %s): %+v", conn.ConnData.RemoteAddr.String(), conn.ConnData.LocalAddr.String(), pkt.Content) - - typ, _, err := d.device.RecvPrecheck(pkt) - msgType := core.HeaderTypeToString(typ) - log.Info("Receive [%s] packet (%s -> %s), %d bytes", msgType, addrStr, conn.ConnData.LocalAddr.String(), n) - log.Evaluate("Receive [%s] packet (%s -> %s), %d bytes", msgType, addrStr, conn.ConnData.LocalAddr.String(), n) - if err != nil { - d.device.ReleasePoolPacket(pkt) - log.Warning("Receive [%s] packet (%s -> %s), precheck error: %v", msgType, addrStr, conn.ConnData.LocalAddr.String(), err) - log.Evaluate("Receive [%s] packet (%s -> %s) precheck error: %v", msgType, addrStr, conn.ConnData.LocalAddr.String(), err) - continue - } - - atomic.StoreInt64(&conn.ConnData.LastLocalRecvTime, time.Now().UnixNano()) - - conn.ConnData.ForwardInboundPacket(pkt) - } -} - -func (d *UdpAC) connectionRoutine(conn *UdpConn) { - addrStr := conn.ConnData.RemoteAddr.String() - - defer d.wg.Done() - defer log.Debug("Connection routine: %s stopped", addrStr) - - log.Debug("Connection routine: %s started", addrStr) - - // stop receiving packets and clean up - defer func() { - d.remoteConnectionMutex.Lock() - delete(d.remoteConnectionMap, addrStr) - d.remoteConnectionMutex.Unlock() - - conn.Close() - }() - - for { - select { - case <-d.signals.stop: - return - - case <-conn.ConnData.SetTimeoutSignal: - if conn.ConnData.TimeoutMs <= 0 { - log.Debug("Connection routine closed immediately") - return - } - - case <-time.After(time.Duration(conn.ConnData.TimeoutMs) * time.Millisecond): - // timeout, quit routine - log.Debug("Connection routine idle timeout") - return - - case pkt, ok := <-conn.ConnData.SendQueue: - if !ok { - return - } - if pkt == nil { - continue - } - d.SendPacket(pkt, conn) - - case pkt, ok := <-conn.ConnData.RecvQueue: - if !ok { - return - } - if pkt == nil { - continue - } - log.Debug("Received udp packet len [%d] from addr: %s\n", len(pkt.Content), addrStr) - - if pkt.HeaderType == core.NHP_KPL { - d.device.ReleasePoolPacket(pkt) - log.Info("Receive [NHP_KPL] message (%s -> %s)", addrStr, conn.ConnData.LocalAddr.String()) - continue - } - - if d.device.IsTransactionResponse(pkt.HeaderType) { - // forward to a specific transaction - transactionId := pkt.Counter() - transaction := d.device.FindLocalTransaction(transactionId) - if transaction != nil { - transaction.NextPacketCh <- pkt - continue - } - } - - pd := &core.PacketData{ - BasePacket: pkt, - ConnData: conn.ConnData, - InitTime: atomic.LoadInt64(&conn.ConnData.LastLocalRecvTime), - } - // generic receive - d.device.RecvPacketToMsg(pd) - - case <-conn.ConnData.BlockSignal: - log.Critical("blocking address %s", addrStr) - return - } - } -} - -func (d *UdpAC) recvMessageRoutine() { - defer d.wg.Done() - defer log.Info("recvMessageRoutine stopped") - - log.Info("recvMessageRoutine started") - - for { - select { - case <-d.signals.stop: - return - - case ppd, ok := <-d.recvMsgCh: - if !ok { - return - } - if ppd == nil { - continue - } - - switch ppd.HeaderType { - case core.NHP_AOP: - // deal with NHP_AOP message - go d.HandleACOperations(ppd) - } - } - } -} - -// keep interaction between ac and server in certain time interval to keep outwards ip path active -func (d *UdpAC) maintainServerConnectionRoutine() { - defer d.wg.Done() - defer log.Info("maintainServerConnectionRoutine stopped") - - log.Info("maintainServerConnectionRoutine started") - - // reset iptables before exiting - defer d.iptables.ResetAllInput() - - var discoveryRoutineWg sync.WaitGroup - defer discoveryRoutineWg.Wait() - - for { - // make a local copy of servers then iterate because next operations are time consuming (too long to use locked iteration) - d.serverPeerMutex.Lock() - var serverCount int32 = int32(len(d.serverPeerMap)) - discoveryQuitArr := make([]chan struct{}, 0, serverCount) - discoveryFailStatusArr := make([]*int32, 0, serverCount) - - for _, server := range d.serverPeerMap { - // launch discovery routine for each server - fail := new(int32) - discoveryFailStatusArr = append(discoveryFailStatusArr, fail) - quit := make(chan struct{}) - discoveryQuitArr = append(discoveryQuitArr, quit) - - discoveryRoutineWg.Add(1) - go d.serverDiscovery(server, &discoveryRoutineWg, fail, quit) - } - d.serverPeerMutex.Unlock() - - // check whether all server discovery failed. - // If so, open all blocked input - quitCheck := make(chan struct{}) - discoveryQuitArr = append(discoveryQuitArr, quitCheck) - discoveryRoutineWg.Add(1) - go func() { - defer discoveryRoutineWg.Done() - - for { - select { - case <-d.signals.stop: - return - case <-quitCheck: - return - case <-time.After(MinialServerDiscoveryInterval * time.Second): - var totalFail int32 - for _, status := range discoveryFailStatusArr { - totalFail += atomic.LoadInt32(status) - } - - if totalFail < int32(len(discoveryFailStatusArr)) { - d.iptables.ResetAllInput() - } else { - d.iptables.AcceptAllInput() - } - } - } - }() - - select { - case <-d.signals.stop: - return - case _, ok := <-d.signals.serverMapUpdated: - if !ok { - return - } - // stop all current discovery routines - for _, q := range discoveryQuitArr { - close(q) - } - // continue and restart with new server discovery cycle - } - } -} - -func (d *UdpAC) serverDiscovery(server *core.UdpPeer, discoveryRoutineWg *sync.WaitGroup, serverFailCount *int32, quit <-chan struct{}) { - defer discoveryRoutineWg.Done() - - acId := d.config.ACId - serverAddr := server.HostOrAddr() - server, sendAddr := d.ResolvePeer(server) - if sendAddr == nil { - log.Error("Cannot connect to nil server address") - return - } - - addrStr := sendAddr.String() - - defer log.Info("server discovery sub-routine at %s stopped", serverAddr) - log.Info("server discovery sub-routine at %s started", serverAddr) - - var failCount int - - for { - var lastSendTime int64 - var lastRecvTime int64 - var connected bool - - // find whether connection is already connected - d.remoteConnectionMutex.Lock() - conn, found := d.remoteConnectionMap[addrStr] - d.remoteConnectionMutex.Unlock() - - if found { - // connection based timing - lastSendTime = atomic.LoadInt64(&conn.ConnData.LastLocalSendTime) - lastRecvTime = atomic.LoadInt64(&conn.ConnData.LastLocalRecvTime) - connected = conn.connected.Load() - } else { - // peer based timing - conn = nil - lastSendTime = server.LastSendTime() - lastRecvTime = server.LastRecvTime() - } - - currTime := time.Now().UnixNano() - peerPbk := server.PublicKey() - - // when a server is not connected, try to connect in every ACLocalTransactionResponseTimeoutMs - // when a server is connected when ServerConnectionInterval is reached since last receive, try resend NHP_AOL for maintaining server connection - if !connected || (currTime-lastRecvTime) > int64(ReportToServerInterval*time.Second) { - // send NHP_AOL message to server - aolMsg := &common.ACOnlineMsg{ - ACId: acId, - AuthServiceId: d.config.AuthServiceId, - ResourceIds: d.config.ResourceIds, - } - aolBytes, _ := json.Marshal(aolMsg) - - aolMd := &core.MsgData{ - RemoteAddr: sendAddr.(*net.UDPAddr), - HeaderType: core.NHP_AOL, - TransactionId: d.device.NextCounterIndex(), - Compress: true, - PeerPk: peerPbk, - Message: aolBytes, - ResponseMsgCh: make(chan *core.PacketParserData), - } - - if !d.IsRunning() { - log.Error("ac(%s#%d)[ACOnline] MsgData channel closed or being closed, skip sending", acId, aolMd.TransactionId) - return - } - - d.sendMsgCh <- aolMd // create new connection - server.UpdateSend(currTime) - - // block until transaction completes or timeouts - ppd := <-aolMd.ResponseMsgCh - close(aolMd.ResponseMsgCh) - - var err error - func() { - defer func() { - if err != nil { - if conn != nil { - conn.connected.Store(false) - } - - failCount += 1 - if failCount%ServerDiscoveryRetryBeforeFail == 0 { - atomic.StoreInt32(serverFailCount, 1) - // remove failed connection - d.remoteConnectionMutex.Lock() - conn = d.remoteConnectionMap[addrStr] - if conn != nil { - log.Info("server discovery failed, close local connection: %s", conn.ConnData.LocalAddr.String()) - delete(d.remoteConnectionMap, addrStr) - } - d.remoteConnectionMutex.Unlock() - conn.Close() - } - log.Error("ac(%s#%d)[ACOnline] reporting to server %s failed", acId, aolMd.TransactionId, addrStr) - } - - }() - - if ppd.Error != nil { - log.Error("ac(%s#%d)[ACOnline] failed to receive response from server %s: %v", acId, aolMd.TransactionId, addrStr, ppd.Error) - err = ppd.Error - return - } - - if ppd.HeaderType != core.NHP_AAK { - log.Error("ac(%s#%d)[ACOnline] response from server %s has wrong type: %s", acId, aolMd.TransactionId, addrStr, core.HeaderTypeToString(ppd.HeaderType)) - err = common.ErrTransactionRepliedWithWrongType - return - } - - aakMsg := &common.ServerACAckMsg{} - err = json.Unmarshal(ppd.BodyMessage, aakMsg) - if err != nil { - log.Error("ac(%s#%d)[HandleACAck] failed to parse %s message: %v", acId, ppd.SenderTrxId, core.HeaderTypeToString(ppd.HeaderType), err) - return - } - - // server discovery succeeded - failCount = 0 - atomic.StoreInt32(serverFailCount, 0) - d.remoteConnectionMutex.Lock() - conn = d.remoteConnectionMap[addrStr] // conn must be available at this point - conn.connected.Store(true) - conn.externalAddr = aakMsg.ACAddr - d.remoteConnectionMutex.Unlock() - log.Info("ac(%s#%d)[ACOnline] succeed. ac external address is %s, replied by server %s", acId, aolMd.TransactionId, aakMsg.ACAddr, addrStr) - }() - - } else if connected { - if (currTime - lastSendTime) > int64(ServerKeepaliveInterval*time.Second) { - // send NHP_KPL to server if no send happens within ServerKeepaliveInterval - md := &core.MsgData{ - RemoteAddr: sendAddr.(*net.UDPAddr), - HeaderType: core.NHP_KPL, - //PeerPk: peerPbk, // pubkey not needed - TransactionId: d.device.NextCounterIndex(), - } - - d.sendMsgCh <- md // send NHP_KPL to server via existing connection - server.UpdateSend(currTime) - } - } - - select { - case <-d.signals.stop: - return - case <-quit: - return - case <-time.After(MinialServerDiscoveryInterval * time.Second): - // wait for ServerConnectionDiscoveryInterval - } - } -} - -func (d *UdpAC) AddServerPeer(server *core.UdpPeer) { - if server.DeviceType() == core.NHP_SERVER { - d.device.AddPeer(server) - - d.serverPeerMutex.Lock() - d.serverPeerMap[server.PublicKeyBase64()] = server - d.serverPeerMutex.Unlock() - - // renew server connection cycle - if len(d.signals.serverMapUpdated) == 0 { - d.signals.serverMapUpdated <- struct{}{} - } - } -} - -func (d *UdpAC) RemoveServerPeer(serverKey string) { - d.serverPeerMutex.Lock() - beforeSize := len(d.serverPeerMap) - delete(d.serverPeerMap, serverKey) - afterSize := len(d.serverPeerMap) - d.serverPeerMutex.Unlock() - - if beforeSize != afterSize { - // renew server connection cycle - if len(d.signals.serverMapUpdated) == 0 { - d.signals.serverMapUpdated <- struct{}{} - } - } -} - -func (d *UdpAC) GenerateAccessToken(au *AgentUser) string { - hashStr := au.Hash() - timeStr := strconv.FormatInt(time.Now().UnixNano(), 10) - au.hash.Write([]byte(timeStr)) - token := base64.StdEncoding.EncodeToString(au.hash.Sum(nil)) - - d.AgentUserTokenMutex.Lock() - defer d.AgentUserTokenMutex.Unlock() - - tokenMap, found := d.agentUserCodeMap[hashStr[0:1]] - if found { - (*tokenMap)[hashStr] = token - } else { - tokenMap = &map[string]string{hashStr: token} - d.agentUserCodeMap[hashStr[0:1]] = tokenMap - } - - // log.Debug("user %+v, hash: %s", au, hashStr) - // log.Debug("agentUserCodeMap: %+v", d.agentUserCodeMap) - // log.Debug("tokenMap: %+v", d.agentUserCodeMap[hashStr[0:1]]) - return token -} - -func (d *UdpAC) VerifyAccessToken(au *AgentUser, token string) bool { - hashStr := au.Hash() - - d.AgentUserTokenMutex.Lock() - defer d.AgentUserTokenMutex.Unlock() - - // log.Debug("verify access token: %s", token) - // log.Debug("user %+v, hash: %s", au, hashStr) - // log.Debug("agentUserCodeMap: %+v", d.agentUserCodeMap) - // log.Debug("tokenMap: %+v", d.agentUserCodeMap[hashStr[0:1]]) - - tokenMap, found := d.agentUserCodeMap[hashStr[0:1]] - if found { - foundToken, found := (*tokenMap)[hashStr] - if found { - return token == foundToken - } - } - - return false -} - -func (d *UdpAC) DeleteAccessToken(au *AgentUser) { - hashStr := au.Hash() - - d.AgentUserTokenMutex.Lock() - defer d.AgentUserTokenMutex.Unlock() - - tokenMap, found := d.agentUserCodeMap[hashStr[0:1]] - if found { - delete(*tokenMap, hashStr) - if len(*tokenMap) == 0 { - delete(d.agentUserCodeMap, hashStr[0:1]) - } - } -} - -// if the server uses hostname as destination, find the correct peer with the actual IP address -func (d *UdpAC) ResolvePeer(peer *core.UdpPeer) (*core.UdpPeer, net.Addr) { - addr := peer.SendAddr() - if addr == nil { - return peer, nil - } - - if len(peer.Hostname) == 0 { - // peer with fixed ip, no change - return peer, addr - } - - actualIp := peer.ResolvedIp() - if peer.Ip == actualIp { - // peer with the correct resolved address, no change - return peer, addr - } - - d.serverPeerMutex.Lock() - defer d.serverPeerMutex.Unlock() - for _, p := range d.serverPeerMap { - if p.Ip == actualIp { - p.CopyResolveStatus(peer) - return p, addr - } - } - - return peer, addr -} +package ac + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "net" + "path/filepath" + "sync" + "sync/atomic" + "time" + + "github.com/OpenNHP/opennhp/common" + "github.com/OpenNHP/opennhp/core" + "github.com/OpenNHP/opennhp/log" + "github.com/OpenNHP/opennhp/utils" + "github.com/OpenNHP/opennhp/version" +) + +var ( + ExeDirPath string +) + +type UdpAC struct { + config *Config + httpConfig *HttpConfig + iptables *utils.IPTables + ipset *utils.IPSet + + stats struct { + totalRecvBytes uint64 + totalSendBytes uint64 + } + + log *log.Logger + + remoteConnectionMutex sync.Mutex + remoteConnectionMap map[string]*UdpConn // indexed by remote UDP address + + serverPeerMutex sync.Mutex + serverPeerMap map[string]*core.UdpPeer // indexed by server's public key + + tokenStoreMutex sync.Mutex + tokenStore TokenStore + + device *core.Device + httpServer *HttpAC + wg sync.WaitGroup + running atomic.Bool + + signals struct { + stop chan struct{} + serverMapUpdated chan struct{} + } + + recvMsgCh <-chan *core.PacketParserData + sendMsgCh chan *core.MsgData +} + +type UdpConn struct { + ConnData *core.ConnectionData + netConn *net.UDPConn + connected atomic.Bool + externalAddr string +} + +func (c *UdpConn) Close() { + if c.netConn != nil { + c.netConn.Close() + c.ConnData.Close() + } +} + +/* +dirPath: the path of app or shared library entry point +logLevel: 0: silent, 1: error, 2: info, 3: debug, 4: verbose +*/ +func (a *UdpAC) Start(dirPath string, logLevel int) (err error) { + common.ExeDirPath = dirPath + ExeDirPath = dirPath + // init logger + a.log = log.NewLogger("NHP-AC", logLevel, filepath.Join(ExeDirPath, "logs"), "ac") + log.SetGlobalLogger(a.log) + + log.Info("=========================================================") + log.Info("=== NHP-AC %s started ===", version.Version) + log.Info("=== REVISION %s ===", version.CommitId) + log.Info("=== RELEASE %s ===", version.BuildTime) + log.Info("=========================================================") + + // init config + err = a.loadBaseConfig() + if err != nil { + return err + } + + // load http config and turn on http server if needed + a.loadHttpConfig() + + a.iptables, err = utils.NewIPTables() + if err != nil { + log.Error("iptables command not found") + return + } + + a.ipset, err = utils.NewIPSet(false) + if err != nil { + log.Error("ipset command not found") + return + } + + prk, err := base64.StdEncoding.DecodeString(a.config.PrivateKeyBase64) + if err != nil { + log.Error("private key parse error %v\n", err) + return fmt.Errorf("private key parse error %v", err) + } + + a.device = core.NewDevice(core.NHP_AC, prk, nil) + if a.device == nil { + log.Critical("failed to create device %v\n", err) + return fmt.Errorf("failed to create device %v", err) + } + + a.remoteConnectionMap = make(map[string]*UdpConn) + a.serverPeerMap = make(map[string]*core.UdpPeer) + a.tokenStore = make(TokenStore) + + // load peers + a.loadPeers() + + a.signals.stop = make(chan struct{}) + a.signals.serverMapUpdated = make(chan struct{}, 1) + + a.recvMsgCh = a.device.DecryptedMsgQueue + a.sendMsgCh = make(chan *core.MsgData, core.SendQueueSize) + + // start device routines + a.device.Start() + + // start ac routines + a.wg.Add(4) + go a.tokenStoreRefreshRoutine() + go a.sendMessageRoutine() + go a.recvMessageRoutine() + go a.maintainServerConnectionRoutine() + + a.running.Store(true) + return nil +} + +func (ac *UdpAC) Stop() { + ac.running.Store(false) + close(ac.signals.stop) + + ac.device.Stop() + ac.StopConfigWatch() + ac.wg.Wait() + close(ac.sendMsgCh) + close(ac.signals.serverMapUpdated) + + log.Info("==========================") + log.Info("=== NHP-AC stopped ===") + log.Info("==========================") + ac.log.Close() +} + +func (a *UdpAC) IsRunning() bool { + return a.running.Load() +} + +func (a *UdpAC) newConnection(addr *net.UDPAddr) (conn *UdpConn) { + conn = &UdpConn{} + var err error + // unlike tcp, udp dial is fast (just socket bind), so no need to run in a thread + conn.netConn, err = net.DialUDP("udp", nil, addr) + if err != nil { + log.Error("could not connect to remote addr %s", addr.String()) + return nil + } + + // retrieve local port + laddr := conn.netConn.LocalAddr() + localAddr, err := net.ResolveUDPAddr(laddr.Network(), laddr.String()) + if err != nil { + log.Error("resolve local UDPAddr error %v\n", err) + return nil + } + + log.Info("Dial up new UDP connection from %s to %s", localAddr.String(), addr.String()) + + conn.ConnData = &core.ConnectionData{ + Device: a.device, + CookieStore: &core.CookieStore{}, + RemoteTransactionMap: make(map[uint64]*core.RemoteTransaction), + LocalAddr: localAddr, + RemoteAddr: addr, + TimeoutMs: DefaultConnectionTimeoutMs, + SendQueue: make(chan *core.Packet, PacketQueueSizePerConnection), + RecvQueue: make(chan *core.Packet, PacketQueueSizePerConnection), + BlockSignal: make(chan struct{}), + SetTimeoutSignal: make(chan struct{}), + StopSignal: make(chan struct{}), + } + + // start connection receive routine + conn.ConnData.Add(1) + go a.recvPacketRoutine(conn) + + return conn +} + +func (a *UdpAC) sendMessageRoutine() { + defer a.wg.Done() + defer log.Info("sendMessageRoutine stopped") + + log.Info("sendMessageRoutine started") + + for { + select { + case <-a.signals.stop: + return + + case md, ok := <-a.sendMsgCh: + if !ok { + return + } + if md == nil || md.RemoteAddr == nil { + log.Warning("Invalid initiator session starter") + continue + } + + addrStr := md.RemoteAddr.String() + + a.remoteConnectionMutex.Lock() + conn, found := a.remoteConnectionMap[addrStr] + a.remoteConnectionMutex.Unlock() + + if found { + md.ConnData = conn.ConnData + } else { + conn = a.newConnection(md.RemoteAddr) + if conn == nil { + log.Error("Failed to dial to remote address: %s", addrStr) + continue + } + + a.remoteConnectionMutex.Lock() + a.remoteConnectionMap[addrStr] = conn + a.remoteConnectionMutex.Unlock() + + md.ConnData = conn.ConnData + + // launch connection routine + a.wg.Add(1) + go a.connectionRoutine(conn) + } + + a.device.SendMsgToPacket(md) + } + } +} + +func (a *UdpAC) SendPacket(pkt *core.Packet, conn *UdpConn) (n int, err error) { + defer func() { + atomic.AddUint64(&a.stats.totalSendBytes, uint64(n)) + atomic.StoreInt64(&conn.ConnData.LastLocalSendTime, time.Now().UnixNano()) + + if !pkt.KeepAfterSend { + a.device.ReleasePoolPacket(pkt) + } + }() + + pktType := core.HeaderTypeToString(pkt.HeaderType) + //log.Debug("Send [%s] packet (%s -> %s): %+v", pktType, conn.ConnData.LocalAddr.String(), conn.ConnData.RemoteAddr.String(), pkt.Content) + log.Info("Send [%s] packet (%s -> %s), %d bytes", pktType, conn.ConnData.LocalAddr.String(), conn.ConnData.RemoteAddr.String(), len(pkt.Content)) + log.Evaluate("Send [%s] packet (%s -> %s, %d bytes)", pktType, conn.ConnData.LocalAddr.String(), conn.ConnData.RemoteAddr.String(), len(pkt.Content)) + return conn.netConn.Write(pkt.Content) +} + +func (a *UdpAC) recvPacketRoutine(conn *UdpConn) { + addrStr := conn.ConnData.RemoteAddr.String() + + defer conn.ConnData.Done() + defer log.Debug("recvPacketRoutine for %s stopped", addrStr) + + log.Debug("recvPacketRoutine for %s started", addrStr) + + for { + select { + case <-conn.ConnData.StopSignal: + return + + default: + } + + // udp recv, blocking until packet arrives or netConn.Close() + pkt := a.device.AllocatePoolPacket() + n, err := conn.netConn.Read(pkt.Buf[:]) + if err != nil { + a.device.ReleasePoolPacket(pkt) + if n == 0 { + // udp connection closed, it is not an error + return + } + log.Error("Failed to receive from remote address %s (%v)", addrStr, err) + continue + } + + // add total recv bytes + atomic.AddUint64(&a.stats.totalRecvBytes, uint64(n)) + + // check minimal length + if n < core.HeaderSize { + a.device.ReleasePoolPacket(pkt) + log.Error("Received UDP packet from %s is too short, discard", addrStr) + continue + } + + pkt.Content = pkt.Buf[:n] + //log.Trace("receive udp packet (%s -> %s): %+v", conn.ConnData.RemoteAddr.String(), conn.ConnData.LocalAddr.String(), pkt.Content) + + typ, _, err := a.device.RecvPrecheck(pkt) + msgType := core.HeaderTypeToString(typ) + log.Info("Receive [%s] packet (%s -> %s), %d bytes", msgType, addrStr, conn.ConnData.LocalAddr.String(), n) + log.Evaluate("Receive [%s] packet (%s -> %s), %d bytes", msgType, addrStr, conn.ConnData.LocalAddr.String(), n) + if err != nil { + a.device.ReleasePoolPacket(pkt) + log.Warning("Receive [%s] packet (%s -> %s), precheck error: %v", msgType, addrStr, conn.ConnData.LocalAddr.String(), err) + log.Evaluate("Receive [%s] packet (%s -> %s) precheck error: %v", msgType, addrStr, conn.ConnData.LocalAddr.String(), err) + continue + } + + atomic.StoreInt64(&conn.ConnData.LastLocalRecvTime, time.Now().UnixNano()) + + conn.ConnData.ForwardInboundPacket(pkt) + } +} + +func (a *UdpAC) connectionRoutine(conn *UdpConn) { + addrStr := conn.ConnData.RemoteAddr.String() + + defer a.wg.Done() + defer log.Debug("Connection routine: %s stopped", addrStr) + + log.Debug("Connection routine: %s started", addrStr) + + // stop receiving packets and clean up + defer func() { + a.remoteConnectionMutex.Lock() + delete(a.remoteConnectionMap, addrStr) + a.remoteConnectionMutex.Unlock() + + conn.Close() + }() + + for { + select { + case <-a.signals.stop: + return + + case <-conn.ConnData.SetTimeoutSignal: + if conn.ConnData.TimeoutMs <= 0 { + log.Debug("Connection routine closed immediately") + return + } + + case <-time.After(time.Duration(conn.ConnData.TimeoutMs) * time.Millisecond): + // timeout, quit routine + log.Debug("Connection routine idle timeout") + return + + case pkt, ok := <-conn.ConnData.SendQueue: + if !ok { + return + } + if pkt == nil { + continue + } + a.SendPacket(pkt, conn) + + case pkt, ok := <-conn.ConnData.RecvQueue: + if !ok { + return + } + if pkt == nil { + continue + } + log.Debug("Received udp packet len [%d] from addr: %s\n", len(pkt.Content), addrStr) + + if pkt.HeaderType == core.NHP_KPL { + a.device.ReleasePoolPacket(pkt) + log.Info("Receive [NHP_KPL] message (%s -> %s)", addrStr, conn.ConnData.LocalAddr.String()) + continue + } + + if a.device.IsTransactionResponse(pkt.HeaderType) { + // forward to a specific transaction + transactionId := pkt.Counter() + transaction := a.device.FindLocalTransaction(transactionId) + if transaction != nil { + transaction.NextPacketCh <- pkt + continue + } + } + + pd := &core.PacketData{ + BasePacket: pkt, + ConnData: conn.ConnData, + InitTime: atomic.LoadInt64(&conn.ConnData.LastLocalRecvTime), + } + // generic receive + a.device.RecvPacketToMsg(pd) + + case <-conn.ConnData.BlockSignal: + log.Critical("blocking address %s", addrStr) + return + } + } +} + +func (a *UdpAC) recvMessageRoutine() { + defer a.wg.Done() + defer log.Info("recvMessageRoutine stopped") + + log.Info("recvMessageRoutine started") + + for { + select { + case <-a.signals.stop: + return + + case ppd, ok := <-a.recvMsgCh: + if !ok { + return + } + if ppd == nil { + continue + } + + switch ppd.HeaderType { + case core.NHP_AOP: + // deal with NHP_AOP message + a.wg.Add(1) + go a.HandleUdpACOperations(ppd) + } + } + } +} + +// keep interaction between ac and server in certain time interval to keep outwards ip path active +func (a *UdpAC) maintainServerConnectionRoutine() { + defer a.wg.Done() + defer log.Info("maintainServerConnectionRoutine stopped") + + log.Info("maintainServerConnectionRoutine started") + + // reset iptables before exiting + defer a.iptables.ResetAllInput() + + var discoveryRoutineWg sync.WaitGroup + defer discoveryRoutineWg.Wait() + + for { + // make a local copy of servers then iterate because next operations are time consuming (too long to use locked iteration) + a.serverPeerMutex.Lock() + var serverCount int32 = int32(len(a.serverPeerMap)) + discoveryQuitArr := make([]chan struct{}, 0, serverCount) + discoveryFailStatusArr := make([]*int32, 0, serverCount) + + for _, server := range a.serverPeerMap { + // launch discovery routine for each server + fail := new(int32) + discoveryFailStatusArr = append(discoveryFailStatusArr, fail) + quit := make(chan struct{}) + discoveryQuitArr = append(discoveryQuitArr, quit) + + discoveryRoutineWg.Add(1) + go a.serverDiscovery(server, &discoveryRoutineWg, fail, quit) + } + a.serverPeerMutex.Unlock() + + // check whether all server discovery failed. + // If so, open all blocked input + quitCheck := make(chan struct{}) + discoveryQuitArr = append(discoveryQuitArr, quitCheck) + discoveryRoutineWg.Add(1) + go func() { + defer discoveryRoutineWg.Done() + + for { + select { + case <-a.signals.stop: + return + case <-quitCheck: + return + case <-time.After(MinialServerDiscoveryInterval * time.Second): + var totalFail int32 + for _, status := range discoveryFailStatusArr { + totalFail += atomic.LoadInt32(status) + } + + if totalFail < int32(len(discoveryFailStatusArr)) { + a.iptables.ResetAllInput() + } else { + a.iptables.AcceptAllInput() + } + } + } + }() + + select { + case <-a.signals.stop: + return + case _, ok := <-a.signals.serverMapUpdated: + if !ok { + return + } + // stop all current discovery routines + for _, q := range discoveryQuitArr { + close(q) + } + // continue and restart with new server discovery cycle + } + } +} + +func (a *UdpAC) serverDiscovery(server *core.UdpPeer, discoveryRoutineWg *sync.WaitGroup, serverFailCount *int32, quit <-chan struct{}) { + defer discoveryRoutineWg.Done() + + acId := a.config.ACId + serverAddr := server.HostOrAddr() + server, sendAddr := a.ResolvePeer(server) + if sendAddr == nil { + log.Error("Cannot connect to nil server address") + return + } + + addrStr := sendAddr.String() + + defer log.Info("server discovery sub-routine at %s stopped", serverAddr) + log.Info("server discovery sub-routine at %s started", serverAddr) + + var failCount int + + for { + var lastSendTime int64 + var lastRecvTime int64 + var connected bool + + // find whether connection is already connected + a.remoteConnectionMutex.Lock() + conn, found := a.remoteConnectionMap[addrStr] + a.remoteConnectionMutex.Unlock() + + if found { + // connection based timing + lastSendTime = atomic.LoadInt64(&conn.ConnData.LastLocalSendTime) + lastRecvTime = atomic.LoadInt64(&conn.ConnData.LastLocalRecvTime) + connected = conn.connected.Load() + } else { + // peer based timing + conn = nil + lastSendTime = server.LastSendTime() + lastRecvTime = server.LastRecvTime() + } + + currTime := time.Now().UnixNano() + peerPbk := server.PublicKey() + + // when a server is not connected, try to connect in every ACLocalTransactionResponseTimeoutMs + // when a server is connected when ServerConnectionInterval is reached since last receive, try resend NHP_AOL for maintaining server connection + if !connected || (currTime-lastRecvTime) > int64(ReportToServerInterval*time.Second) { + // send NHP_AOL message to server + aolMsg := &common.ACOnlineMsg{ + ACId: acId, + AuthServiceId: a.config.AuthServiceId, + ResourceIds: a.config.ResourceIds, + } + aolBytes, _ := json.Marshal(aolMsg) + + aolMd := &core.MsgData{ + RemoteAddr: sendAddr.(*net.UDPAddr), + HeaderType: core.NHP_AOL, + TransactionId: a.device.NextCounterIndex(), + Compress: true, + PeerPk: peerPbk, + Message: aolBytes, + ResponseMsgCh: make(chan *core.PacketParserData), + } + + if !a.IsRunning() { + log.Error("ac(%s#%d)[ACOnline] MsgData channel closed or being closed, skip sending", acId, aolMd.TransactionId) + return + } + + a.sendMsgCh <- aolMd // create new connection + server.UpdateSend(currTime) + + // block until transaction completes or timeouts + ppd := <-aolMd.ResponseMsgCh + close(aolMd.ResponseMsgCh) + + var err error + func() { + defer func() { + if err != nil { + if conn != nil { + conn.connected.Store(false) + } + + failCount += 1 + if failCount%ServerDiscoveryRetryBeforeFail == 0 { + atomic.StoreInt32(serverFailCount, 1) + // remove failed connection + a.remoteConnectionMutex.Lock() + conn = a.remoteConnectionMap[addrStr] + if conn != nil { + log.Info("server discovery failed, close local connection: %s", conn.ConnData.LocalAddr.String()) + delete(a.remoteConnectionMap, addrStr) + } + a.remoteConnectionMutex.Unlock() + conn.Close() + } + log.Error("ac(%s#%d)[ACOnline] reporting to server %s failed", acId, aolMd.TransactionId, addrStr) + } + + }() + + if ppd.Error != nil { + log.Error("ac(%s#%d)[ACOnline] failed to receive response from server %s: %v", acId, aolMd.TransactionId, addrStr, ppd.Error) + err = ppd.Error + return + } + + if ppd.HeaderType != core.NHP_AAK { + log.Error("ac(%s#%d)[ACOnline] response from server %s has wrong type: %s", acId, aolMd.TransactionId, addrStr, core.HeaderTypeToString(ppd.HeaderType)) + err = common.ErrTransactionRepliedWithWrongType + return + } + + aakMsg := &common.ServerACAckMsg{} + err = json.Unmarshal(ppd.BodyMessage, aakMsg) + if err != nil { + log.Error("ac(%s#%d)[HandleACAck] failed to parse %s message: %v", acId, ppd.SenderTrxId, core.HeaderTypeToString(ppd.HeaderType), err) + return + } + + // server discovery succeeded + failCount = 0 + atomic.StoreInt32(serverFailCount, 0) + a.remoteConnectionMutex.Lock() + conn = a.remoteConnectionMap[addrStr] // conn must be available at this point + conn.connected.Store(true) + conn.externalAddr = aakMsg.ACAddr + a.remoteConnectionMutex.Unlock() + log.Info("ac(%s#%d)[ACOnline] succeed. ac external address is %s, replied by server %s", acId, aolMd.TransactionId, aakMsg.ACAddr, addrStr) + }() + + } else if connected { + if (currTime - lastSendTime) > int64(ServerKeepaliveInterval*time.Second) { + // send NHP_KPL to server if no send happens within ServerKeepaliveInterval + md := &core.MsgData{ + RemoteAddr: sendAddr.(*net.UDPAddr), + HeaderType: core.NHP_KPL, + //PeerPk: peerPbk, // pubkey not needed + TransactionId: a.device.NextCounterIndex(), + } + + a.sendMsgCh <- md // send NHP_KPL to server via existing connection + server.UpdateSend(currTime) + } + } + + select { + case <-a.signals.stop: + return + case <-quit: + return + case <-time.After(MinialServerDiscoveryInterval * time.Second): + // wait for ServerConnectionDiscoveryInterval + } + } +} + +func (a *UdpAC) AddServerPeer(server *core.UdpPeer) { + if server.DeviceType() == core.NHP_SERVER { + a.device.AddPeer(server) + + a.serverPeerMutex.Lock() + a.serverPeerMap[server.PublicKeyBase64()] = server + a.serverPeerMutex.Unlock() + + // renew server connection cycle + if len(a.signals.serverMapUpdated) == 0 { + a.signals.serverMapUpdated <- struct{}{} + } + } +} + +func (a *UdpAC) RemoveServerPeer(serverKey string) { + a.serverPeerMutex.Lock() + beforeSize := len(a.serverPeerMap) + delete(a.serverPeerMap, serverKey) + afterSize := len(a.serverPeerMap) + a.serverPeerMutex.Unlock() + + if beforeSize != afterSize { + // renew server connection cycle + if len(a.signals.serverMapUpdated) == 0 { + a.signals.serverMapUpdated <- struct{}{} + } + } +} + +// if the server uses hostname as destination, find the correct peer with the actual IP address +func (a *UdpAC) ResolvePeer(peer *core.UdpPeer) (*core.UdpPeer, net.Addr) { + addr := peer.SendAddr() + if addr == nil { + return peer, nil + } + + if len(peer.Hostname) == 0 { + // peer with fixed ip, no change + return peer, addr + } + + actualIp := peer.ResolvedIp() + if peer.Ip == actualIp { + // peer with the correct resolved address, no change + return peer, addr + } + + a.serverPeerMutex.Lock() + defer a.serverPeerMutex.Unlock() + for _, p := range a.serverPeerMap { + if p.Ip == actualIp { + p.CopyResolveStatus(peer) + return p, addr + } + } + + return peer, addr +} diff --git a/agent/main/etc/server.toml b/agent/main/etc/server.toml index b713a115..b4f73389 100644 --- a/agent/main/etc/server.toml +++ b/agent/main/etc/server.toml @@ -7,7 +7,7 @@ # ExpireTime (epoch timestamp in seconds): peer key validation will fail when it expires. [[Servers]] Hostname = "" -Ip = "192.168.56.101" +Ip = "192.168.80.35" Port = 62206 PubKeyBase64 = "WqJxe+Z4+wLen3VRgZx6YnbjvJFmptz99zkONCt/7gc=" -ExpireTime = 1716345064 +ExpireTime = 1924991999 diff --git a/common/nhpmsg.go b/common/nhpmsg.go index cd800dce..dd1bbc4b 100644 --- a/common/nhpmsg.go +++ b/common/nhpmsg.go @@ -70,14 +70,15 @@ type PreAccessInfo struct { } type ServerKnockAckMsg struct { - ErrCode string `json:"errCode"` - ErrMsg string `json:"errMsg,omitempty"` - ResourceHost map[string]string `json:"resHost"` - OpenTime uint32 `json:"opnTime"` - AuthProviderToken string `json:"aspToken,omitempty"` // optional for ac backend validation - AgentAddr string `json:"agentAddr"` - PreAccessActions []*PreAccessInfo `json:"preActs,omitempty"` // optional for pre-access - RedirectUrl string `json:"redirectUrl,omitempty"` + ErrCode string `json:"errCode"` + ErrMsg string `json:"errMsg,omitempty"` + ResourceHost map[string]string `json:"resHost"` + OpenTime uint32 `json:"opnTime"` + AuthProviderToken string `json:"aspToken,omitempty"` // optional for ac backend validation + AgentAddr string `json:"agentAddr"` + ACTokens map[string]string `json:"acTokens"` + PreAccessActions map[string]*PreAccessInfo `json:"preActions,omitempty"` // optional for pre-access + RedirectUrl string `json:"redirectUrl,omitempty"` } type AgentListMsg struct { @@ -125,6 +126,7 @@ type ACOpsResultMsg struct { ErrCode string `json:"errCode"` ErrMsg string `json:"errMsg,omitempty"` OpenTime uint32 `json:"opnTime"` + ACToken string `json:"token"` PreAccessAction *PreAccessInfo `json:"preAct"` } @@ -134,6 +136,11 @@ type ACOnlineMsg struct { ACId string `json:"acId,omitempty"` } +type ACRefreshMsg struct { + NhpToken string `json:"nhpToken"` + SourceAddr *NetAddress `json:"srcAddr"` +} + type ServerACAckMsg struct { ErrCode string `json:"errCode"` ErrMsg string `json:"errMsg,omitempty"` diff --git a/common/types.go b/common/types.go index ddb9cab9..a74f5b35 100644 --- a/common/types.go +++ b/common/types.go @@ -2,6 +2,14 @@ package common import "net/url" +// an object contains represent knocking user information +type AgentUser struct { + UserId string + DeviceId string + OrganizationId string + AuthServiceId string +} + // authsvcprovider and resource type LoginPageContext struct { Title string `json:"title,omitempty"` @@ -76,3 +84,8 @@ type HttpKnockRequest struct { SrcIp string `json:"-"` SrcPort int `json:"-"` } + +type HttpRefreshRequest struct { + Token string `json:"token"` + SrcIp string `json:"srcIp"` +} diff --git a/core/initiator.go b/core/initiator.go index e3c77854..ac5be0b2 100644 --- a/core/initiator.go +++ b/core/initiator.go @@ -119,6 +119,7 @@ func (d *Device) createMsgAssemblerData(md *MsgData) (mad *MsgAssemblerData, err // create header and init device ecdh switch mad.CipherScheme { case CIPHER_SCHEME_CURVE: + log.Info("start encryption using CIPHER_SCHEME_CURVE") mad.header = (*curve.HeaderCurve)(unsafe.Pointer(&mad.BasePacket.Buf[0])) mad.ciphers = NewCipherSuite(CIPHER_SCHEME_CURVE) mad.deviceEcdh = d.staticEcdhCurve @@ -126,6 +127,7 @@ func (d *Device) createMsgAssemblerData(md *MsgData) (mad *MsgAssemblerData, err case CIPHER_SCHEME_GMSM: fallthrough default: + log.Info("start encryption using CIPHER_SCHEME_GMSM") mad.header = (*gmsm.HeaderGmsm)(unsafe.Pointer(&mad.BasePacket.Buf[0])) mad.ciphers = NewCipherSuite(CIPHER_SCHEME_GMSM) mad.deviceEcdh = d.staticEcdhGmsm @@ -280,6 +282,8 @@ func (mad *MsgAssemblerData) setPeerPublicKey(peerPk []byte) (err error) { static = aead.Seal(mad.header.StaticBytes()[:0], mad.header.NonceBytes(), mad.deviceEcdh.PublicKey(), mad.chainHash.Sum(nil)) } + //log.Debug("encrypted pubkey: %v, output: %v", mad.deviceEcdh.PublicKey(), static) + // evolve chainhash ChainHash1 -> ChainHash2 mad.chainHash.Write(static) @@ -353,6 +357,7 @@ func (mad *MsgAssemblerData) encryptBody() (err error) { return err } body = buf.Bytes() + //log.Debug("message compressed: %v -> %v", mad.bodyMessage, body) mad.BodySize = len(body) + GCMTagSize // set header flag @@ -383,7 +388,9 @@ func (mad *MsgAssemblerData) encryptBody() (err error) { mad.addHMAC(mad.HeaderType == NHP_RKN) // encrypt body and write into mad.BasePacket.Buf space - mad.bodyAead.Seal(mad.BasePacket.Buf[mad.header.Size():mad.header.Size()], mad.header.NonceBytes(), body, mad.chainHash.Sum(nil)) + ciphertext := mad.bodyAead.Seal(mad.BasePacket.Buf[mad.header.Size():mad.header.Size()], mad.header.NonceBytes(), body, mad.chainHash.Sum(nil)) + _ = ciphertext + //log.Debug("encrypted body: %v, output: %v", body, ciphertext) // set valid packet mad.BasePacket.Content = mad.BasePacket.Buf[:packetLen] diff --git a/core/responder.go b/core/responder.go index ba6972c9..7a65df14 100644 --- a/core/responder.go +++ b/core/responder.go @@ -108,6 +108,7 @@ func (d *Device) createPacketParserData(pd *PacketData) (ppd *PacketParserData, // init header and init device ecdh ppd.HeaderFlag = binary.BigEndian.Uint16(ppd.basePacket.Content[10:12]) if ppd.HeaderFlag&NHP_FLAG_EXTENDEDLENGTH == 0 { + log.Info("start decryption using CIPHER_SCHEME_CURVE") ppd.CipherScheme = CIPHER_SCHEME_CURVE ppd.header = (*curve.HeaderCurve)(unsafe.Pointer(&ppd.basePacket.Content[0])) ppd.Ciphers = NewCipherSuite(CIPHER_SCHEME_CURVE) @@ -118,6 +119,7 @@ func (d *Device) createPacketParserData(pd *PacketData) (ppd *PacketParserData, case NHP_FLAG_SCHEME_GMSM: fallthrough default: + log.Info("start decryption using CIPHER_SCHEME_GMSM") ppd.CipherScheme = CIPHER_SCHEME_GMSM ppd.header = (*gmsm.HeaderGmsm)(unsafe.Pointer(&ppd.basePacket.Content[0])) ppd.Ciphers = NewCipherSuite(CIPHER_SCHEME_GMSM) @@ -277,6 +279,8 @@ func (ppd *PacketParserData) validatePeer() (err error) { peerPk = peerPk[:PublicKeySize] } + //log.Debug("decrypted pubkey: %v, input: %v", peerPk, ppd.header.StaticBytes()) + // validate peer public key if they already exists in peer pool // also validate peer address if it has been changed // NOTE: to relieve ac from managing arbitrary agent peers, @@ -463,6 +467,8 @@ func (ppd *PacketParserData) decryptBody() (err error) { return err } + //log.Debug("decrypted body: %v, input: %v", body, ppd.basePacket.Content[ppd.header.Size():]) + // Note: ppd.BodyMessage must be a separate []byte slice because ppd.BasePacket.Buf will be released later if ppd.BodyCompress { // decompress @@ -479,9 +485,10 @@ func (ppd *PacketParserData) decryptBody() (err error) { return err } - ppd.BodyMessage = buf.Bytes() + ppd.BodyMessage = buf.Bytes() // separately allocated memory + //log.Debug("message decompressed %v -> %v", body, ppd.BodyMessage) } else { - ppd.BodyMessage = append(ppd.BodyMessage, body...) + ppd.BodyMessage = append(ppd.BodyMessage, body...) // deep copy } return nil diff --git a/core/scheme/curve/header.go b/core/scheme/curve/header.go index 5ebf4241..d5412439 100644 --- a/core/scheme/curve/header.go +++ b/core/scheme/curve/header.go @@ -69,7 +69,7 @@ func (h *HeaderCurve) Flag() uint16 { func (h *HeaderCurve) SetFlag(flag uint16) { flag &= ^uint16(NHP_FLAG_EXTENDEDLENGTH) - flag &= 0xF << 12 + flag &= 0x0FFF binary.BigEndian.PutUint16(h.HeaderCommon[10:12], flag) } diff --git a/core/scheme/gmsm/header.go b/core/scheme/gmsm/header.go index 89d65205..66201b74 100644 --- a/core/scheme/gmsm/header.go +++ b/core/scheme/gmsm/header.go @@ -74,7 +74,7 @@ func (h *HeaderGmsm) Flag() uint16 { func (h *HeaderGmsm) SetFlag(flag uint16) { flag |= uint16(NHP_FLAG_EXTENDEDLENGTH) - flag &= 0xF << 12 + flag &= 0x0FFF flag |= NHP_FLAG_SCHEME_GMSM << 12 binary.BigEndian.PutUint16(h.HeaderCommon[10:12], flag) } diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index f7ac156f..47f5f7ec 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -80,7 +80,7 @@ GEM terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) unicode-display_width (2.5.0) - webrick (1.8.1) + webrick (1.8.2) PLATFORMS arm64-darwin diff --git a/docs/_config.yml b/docs/_config.yml index e8b1884c..e5dc56f6 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -25,7 +25,7 @@ baseurl: "" # the subpath of your site, e.g. /blog theme: just-the-docs url: "https://opennhp.org" -logo: "/images/logo_doc.png" +logo: "/images/logo12.png" favicon_ico: "/favicon.ico" repository: OpenNHP/opennhp # for github-metadata diff --git a/docs/about.md b/docs/about.md index 83b986f1..32afb01f 100644 --- a/docs/about.md +++ b/docs/about.md @@ -1,13 +1,16 @@ --- layout: page title: About -nav_order: 8 +nav_order: 9 permalink: /about/ --- # About OpenNHP Project {: .fs-9 } +OpenNHP is developed by a global community of passionate modern security enthusiasts. +{: .fs-6 .fw-300 } + [中文版](/zh-cn/about/){: .label .fs-4 } --- diff --git a/docs/build.md b/docs/build.md index 3349ff46..d116e0ff 100644 --- a/docs/build.md +++ b/docs/build.md @@ -1,13 +1,158 @@ --- layout: page title: How to Build -nav_order: 5 +nav_order: 6 permalink: /build/ --- # Build OpenNHP Source Code {: .fs-9 } +This article explains how to build OpenNHP from source code. +{: .fs-6 .fw-300 } + +[中文版](/zh-cn/build/){: .label .fs-4 } + +--- + +## 1. WSL Environment Setup + +**Note:** You can run Linux through the WSL subsystem on Windows 10/11. For details, see the official WSL documentation: https://learn.microsoft.com/en-us/windows/wsl/install + +- **【Enable the WSL function】** On Win10, you need to enable WSL first to use it for installing Linux. See the settings interface in the image below. + + ![Windows 10 on WSL Settings](/images/win10wsl_en.png) + +- **【Install Linux on WSL】** It is recommended to install Ubuntu Linux on WSL by running the following command through PowerShell: + + ```bat + wsl --update + wsl --install -d Ubuntu + ``` + + If you encounter the following problems, refer to: + + ```text + From 'https://raw.githubusercontent.com/microsoft/WSL/master/distributions/DistributionInfo.json' to extract the distribution list. The server name or address could not be resolved + Error code: Wsl/WININET_E_NAME_NOT_RESOLVED + ``` + +- **【IP address of the WSL environment】** In the Linux environment of WSL, run the following command to get the IP address: + +| Host machine | Command to view the IP address | +| :------------------------: | :-----------------------------------------------------: | +| Linux hosts in WSL | `hostname -I \| awk '{print $1}'` | +| WSL hosts the Windows host | `ip route show \| grep -i default \| awk '{ print $3}'` | + +## 2. System requirement + +- 2.1 'Go Language' environment: **Go 1.21** . Installation package download: + - **Windows and macOS**Environment, install Go through the downloaded installer. + - **Linux** environment can be installed directly through the management tool: `sudo apt install golang` + - After the installation is successful, run the command `go version`to see the Go version number. + - **Windows and macOS**environment,Install Go through the downloaded installer. + - **Linux**Environment can be installed directly through the management tool:`sudo apt install golang` Or install it manually with the following command: + + ```bash + 1. sudo apt-get update + 2. wget https://go.dev/dl/go1.21.0.linux-amd64.tar.gz + 3. sudo tar -xvf go1.21.0.linux-amd64.tar.gz + 4. sudo mv go /usr/local + 5. export GOROOT=/usr/local/go + 6. export GOPATH=$HOME/go + 7. export PATH=$GOPATH/bin:$GOROOT/bin:$PATH + 8. source ~/.profile + ``` + + - After the installation is successful, run the command `go version` to see the Go version number. +- 2.2 `GCC`environment: + - **Linux and macOS**:**GCC 8.0**or above。 + - To view the GCC version of the command:`gcc -v` + - To install GCC: `sudo apt install build-essential` + - **Windows**: + 1. Step 1: **Install mingw64**. mingw64 can be downloaded from msys2's package management tool. Installation requirements, downloads, and installation tutorials for msys2 are available at . + + ![install_msys2](/images/install_msys2.png) + + 2. Step 2: **Install GCC**. Enter the command in msys2's console: + + ```bash + pacman -S mingw-w64-ucrt-x86_64-gcc + ``` + + 3. Step 3: **Configure GCC**. Add the GCC tool PATH to the Windows *%PATH%* environment variable. For example, if the installation path of mingw-w64-gcc is`C:\Program Files\MSYS2\ `, run the command + + ```bat + setx PATH "%PATH%;C:\Program Files\MSYS2\ucrt64\bin + ``` + After successful execution, open a new command line window and check the version number of *gcc* + ```bat + gcc --version + ``` + + - **Tip:** Under Windows can be ` WSL ` subsystem to run Linux, details please see WSL official document: < https://learn.microsoft.com/zh-cn/windows/wsl/install > + - It is recommended to run the latest version of Ubuntu v22 on WSL and install it by running the following command from PowerShell on Windows: + ```bat + wsl --install --distribution Ubuntu-22.04 + ``` + +*Note: If 2.1 and 2.2 are complete, when executing the compile command `.\build.bat `directly in the project directory, you will usually encounter` the system cannot find the specified path `or` 'lib' is not an internal or external command, nor is it a runnable program or batch file`The mistake. 2.3 Provides a solution to this problem for reference.* + +- 2.3 `lib`environment: + + + - The lib utility is used in the compile run command, which is a tool for generating.lib files, usually for linking static libraries or exporting symbol tables (the.lib file is generated in Windows to work with the.dll file). The error message lib is not an internal or external command, indicating that the system cannot find the lib utility. + + - **To solve the problem ('lib' is not an internal or external command, nor is it a runnable program or batch file) :** Install Visual Studio and Visual Studio tools. + + - The lib tool is Microsoft's library management tool and is usually installed with Microsoft Build Tools for Visual Studio. Make sure you have Visual Studio installed and have selected the C++ Build Tools components, including lib.exe. + + - If you do not have Visual Studio installed, you can download and install it from the official Visual Studio website: https://visualstudiomicrosoft.com/zh-hans/ when installation, select the desktop development (c + +) "the workload, it contains the lib. Exe and other necessary tools. + + - After installing Visual Studio, make sure to use the Visual Studio Developer Command Prompt to run the `build.bat` file that contains the lib command. This command line tool automatically loads environment variables for the build tool, such as lib.exe + + - **To resolve the problem (the system cannot find the specified path) :** Change the path in the `build.bat` file + + - Open the `build.bat` file and find it + ```bat + call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 + ``` + + - Change the installation path to your own visual studio directory. For example: + ```bat + call "F:\develop\visualstu\VC\Auxiliary\Build\vcvarsall.bat" x64 + ``` + +## 3. compile + +1. Pull the code repository + + ```bash + git clone https://github.com/OpenNHP/opennhp.git + ``` + +2. Go environment Settings + + ```bash + go env -w GOPROXY="https://goproxy.cn,direct" + ``` + +3. Compile and build + - **Linux and macOS**:Run the script in the code root directory + `make` + - **Windows**:Run the *BAT* file in the code root directory + `build.bat`
+ *(Note: If an error occurs during the compilation process under windows, try this compilation method: In the Visual Studio developer command prompt for VS command window, switch to the project directory and execute the `./build.bat `command)* + +## 4. result + +Compiled binaries are in the code directory under the `release` subdirectory. + +- **NHP-Server** executable and configuration files: `release\nhp-server` subdirectory +- **NHP-AC** executable and configuration files: `release\nhp-ac` subdirectory +- **NHP-Agent** executable and configuration files: `release\nhp-agent` subdirectory +- All binaries are packaged into a `tar` file: `release\archive` subdirectory + [中文版](/zh-cn/build/){: .label .fs-4 } --- diff --git a/docs/code.md b/docs/code.md index d9c7eed6..b91a35d0 100644 --- a/docs/code.md +++ b/docs/code.md @@ -1,13 +1,15 @@ --- layout: page title: Understand the Code -nav_order: 6 +nav_order: 7 permalink: /code/ --- # Understand the Source Code {: .fs-9 } +This article explains how to read the source code of OpenNHP. +{: .fs-6 .fw-300 } [中文版](/zh-cn/code/){: .label .fs-4 } diff --git a/docs/comparison.md b/docs/comparison.md index 0fd3a877..51ecda81 100644 --- a/docs/comparison.md +++ b/docs/comparison.md @@ -1,17 +1,18 @@ --- layout: page title: Comparison -nav_order: 3 +nav_order: 4 permalink: /comparison/ --- -- {: .fs-9 } +# Comparison between NHP and SPA +{: .fs-9 } -*       (Note: The following content is extracted from the journal paper "AHAC: Advanced Network-Hiding Access Control Framework" published in Volume 14, Issue 13 of the journal *Applied Sciences* in 2024. It is worth noting that the AHAC framework is a key component of the NHP (OpenNHP) technology system. This paper provides a detailed introduction and comparison of the performance of NHP (Next-Generation Host Protocol) and SPA (Secure Protocol Architecture) in various aspects.)* +Note: The following content is extracted from the journal paper "AHAC: Advanced Network-Hiding Access Control Framework" published in Volume 14, Issue 13 of the journal *Applied Sciences* in 2024. It is worth noting that the AHAC framework is a key component of the NHP (OpenNHP) technology system. -# Comparison between NHP and SPA +[中文版](/zh-cn/comparison/){: .label .fs-4 } -# Table Of Content: +--- - [Comparison between NHP and SPA](#comparison-between-nhp-and-spa) - [Table Of Content:](#table-of-content) @@ -190,6 +191,6 @@ FIDO-based authentication response to the Server. ![Compatibility comparison](/images/Compatibility_comparison.png) -- [中文版](/docs/zh-cn/comparison.zh-cn.md){: .label .fs-4 } + --- \ No newline at end of file diff --git a/docs/cryptography.md b/docs/cryptography.md new file mode 100644 index 00000000..af8313f7 --- /dev/null +++ b/docs/cryptography.md @@ -0,0 +1,291 @@ +--- +layout: page +title: Cryptography +nav_order: 3 +permalink: /cryptography/ +--- + +# Cryptographic Algorithms in OpenNHP +{: .fs-9 } + +Cryptography is at the heart of OpenNHP, providing robust security, excellent performance, and scalability by utilizing cutting-edge cryptographic algorithms. +{: .fs-6 .fw-300 } + +[中文版](/zh-cn/cryptography/){: .label .fs-4 } + +--- + +This article explains how OpenNHP takes advantages of modern cryptographic algorithms in several critical areas: + +1. [Public Key Cryptography](#1-public-key-cryptography) +2. [Key Exchange, Data Encryption and Identity Verification](#2-key-exchange-data-encryption-and-identity-verification) +3. [Key Distribution and Management](#3-key-distribution-and-management) + +## 1) Public Key Cryptography + +### 1.1 Introduction +In the evolving landscape of cybersecurity, securing communications and protecting network resources are essential, especially with the increasing sophistication of cyber threats. The Network Infrastructure Hiding Protocol (NHP), a zero-trust security mechanism, stands at the forefront of efforts to address these concerns by concealing network infrastructure details from attackers and ensuring that only trusted entities can interact with network resources. A key component of NHP's security model is the use of Elliptic Curve Cryptography (ECC) for public key cryptography. In this article, we explore how ECC integrates into the NHP Zero Trust protocol to provide robust and efficient security. + +### 1.2 What is Elliptic Curve Cryptography? + +Elliptic Curve Cryptography (ECC) is a modern approach to public key cryptography that provides equivalent levels of security with significantly smaller key sizes compared to traditional methods such as RSA. ECC relies on the mathematical properties of elliptic curves over finite fields, providing a powerful balance of security and performance. Due to its reduced computational overhead, ECC is particularly suitable for resource-constrained environments such as embedded systems or mobile devices. + +The advantages of ECC include: + +- **Smaller Key Sizes**: ECC achieves a high level of security with smaller keys, which translates to faster operations, less bandwidth usage, and reduced computational requirements. +- **Enhanced Security**: The underlying problem of elliptic curve discrete logarithms is computationally complex, making ECC resistant to common forms of cryptographic attack. +- **Efficiency**: With less processing power needed compared to traditional methods, ECC can handle encryption and decryption more efficiently, which is crucial for zero trust environments requiring frequent cryptographic operations. + + +### 1.3 How NHP Uses ECC for Secure Communication + +NHP uses ECC in key exchange, data encryption, identity verification with the Noise protocol framework, and key distribution and management with Certificateless Public Key Cryptography (CL-PKC). + + +#### 1. **Key Exchange Mechanism** + +The secure exchange of encryption keys between communicating entities is the backbone of any secure communication protocol. NHP uses Elliptic Curve Diffie-Hellman (ECDH) for its key exchange mechanism. In the ECDH key exchange, both communicating parties generate a public-private key pair using elliptic curves. The public keys are then exchanged, allowing both parties to compute a shared secret without ever having to transmit it directly over the network. + +The benefit of using ECDH in NHP is twofold: first, it provides forward secrecy, meaning that even if the private key of one party is compromised in the future, previously established session keys remain secure. Secondly, because of ECC's efficiency, the key exchange process is computationally lightweight, ensuring that key establishment is performed quickly without a large computational footprint. + +#### 2. **Authentication with Digital Signatures** + +In a zero-trust environment, authentication is paramount. NHP utilizes Elliptic Curve Digital Signature Algorithm (ECDSA) to verify the authenticity of entities attempting to access network resources. ECDSA, an ECC-based digital signature scheme, allows devices to prove their identity without revealing sensitive private keys. + +In the NHP protocol, when an entity wants to communicate with the network, it must provide a digital signature generated with its private key. The receiving entity can then use the corresponding public key to verify the validity of the signature. This ensures that only legitimate entities can participate in the network, effectively implementing the zero-trust model's "never trust, always verify" principle. + +#### 3. **Encryption for Data Confidentiality** + +NHP employs symmetric encryption for data confidentiality during communication, but symmetric keys must be securely distributed and shared between entities. ECC plays a role in the secure distribution of these symmetric keys through ECDH, providing an encrypted communication channel where symmetric keys are exchanged securely. + +Once these keys are exchanged, NHP switches to symmetric encryption for data transfer, benefiting from the speed and efficiency of symmetric encryption algorithms. ECC ensures that the symmetric key exchange is both secure and resource-efficient. + +#### 4. **Key Distribution and Management with Certificateless Public Key Cryptography (CL-PKC)** + +NHP also leverages ECC for key distribution and management using Certificateless Public Key Cryptography (CL-PKC). In traditional public key infrastructure, certificates are used to validate public keys, which introduces complexity in terms of certificate management. CL-PKC eliminates the need for certificates by allowing entities to generate partial private keys in collaboration with a trusted authority, while also generating their own key pairs independently. + +This approach simplifies key management and ensures that public keys can be used securely without the overhead of certificate issuance and validation. By using ECC in CL-PKC, NHP provides a lightweight and secure means of key distribution, further enhancing the zero-trust model by removing dependencies on centralized certificate authorities. + +### The Advantages of Using ECC in NHP + +The use of ECC within the NHP zero-trust protocol offers numerous advantages that make it well-suited to its security objectives: + +1. **Scalable Security**: ECC's smaller key sizes provide strong security, which scales well with the increasing computational power of adversaries. With NHP's goal of providing a zero-trust environment for diverse network deployments, ECC's scalability is a critical asset. + +2. **Resource Efficiency**: ECC reduces the computational burden on network devices compared to traditional public key cryptography. In environments where network resources may be constrained—such as edge devices or IoT components—this efficiency is essential for maintaining high performance without sacrificing security. + +3. **Improved Performance**: The combination of ECDH for key exchange, ECDSA for authentication, and efficient symmetric encryption provides a balanced solution for secure communications. This balanced approach allows NHP to achieve the goals of zero trust while keeping latency low, which is crucial in time-sensitive network applications. + +### Conclusion + +The integration of Elliptic Curve Cryptography into the NHP Zero Trust Protocol provides a powerful means of securing network communications with minimal performance impact. By leveraging ECDH for secure key exchanges, ECDSA for robust authentication, and efficient symmetric encryption for data transfer, ECC supports the zero-trust model's goals of concealing network infrastructure, ensuring only trusted entities can access resources, and maintaining security with low overhead. + +As cyber threats become more sophisticated, leveraging advanced cryptographic techniques like ECC in protocols like NHP is vital to staying ahead of attackers. The synergy between ECC and NHP not only helps protect critical network infrastructure but also ensures that security measures are both robust and efficient—a key combination for the success of any modern cybersecurity initiative. + + + +## 2) Key Exchange, Data Encryption and Identity Verification +### 2.1 Introduction +The Network Infrastructure Hiding Protocol (NHP) is built around a zero-trust security model, ensuring secure communications even in the presence of potential attackers. To achieve this, NHP integrates the Noise Protocol Framework, a cryptographic framework designed for secure and flexible key exchange, data encryption, and identity verification. This combination provides robust security with minimal computational overhead. + +### 2.2 Key Exchange with Noise Protocol +NHP utilizes the Noise Protocol's key exchange mechanism to ensure secure, authenticated communication channels between parties. The key exchange begins with a handshake phase where both communicating entities exchange Diffie-Hellman (DH) public keys. In Noise, each party generates an ephemeral key pair, and the exchanged keys are used to derive a shared secret, which is then used to encrypt the following communication. + +Noise allows NHP to support both long-term static keys and ephemeral keys for enhanced security. The flexibility of the Noise framework's handshake patterns enables NHP to customize how the handshake occurs based on the specific use case, providing options for mutual authentication, anonymous initiators, or encryption of the initial handshake itself. By leveraging Noise's simple yet powerful token-based handshake system, NHP can precisely control the sequence of key exchange messages while keeping identity information confidential. + +### 2.3 Data Encryption +Once the shared key is derived during the handshake, the Noise framework uses symmetric encryption to secure data. NHP takes advantage of the Noise CipherState and SymmetricState objects, which are core components of Noise's state machine, to manage encryption and decryption keys for the communication session. + +In particular, the shared key is used to initialize a symmetric encryption key (k) along with a nonce (n) for encrypting data. Noise supports advanced encryption schemes like ChaCha20-Poly1305 or AESGCM, providing authenticated encryption with associated data (AEAD) to maintain data confidentiality and integrity. The chaining key (ck) and the handshake hash (h) are used to continuously derive fresh keys during the session, enhancing the forward secrecy and ensuring that a compromise of one key does not jeopardize other parts of the communication. + +NHP benefits from these cryptographic properties by providing encrypted tunnels for network data, ensuring that any intercepted data cannot be decrypted without knowledge of the derived keys, which are securely exchanged during the handshake. + +### 2.4 Identity Verification +Noise provides mechanisms for identity verification by combining the exchange of static keys with Diffie-Hellman operations. In NHP, identity verification occurs during the handshake, where static keys are encrypted and verified through shared DH operations, effectively binding the public keys of both parties to the derived session key. + +During the handshake, Noise uses tokens such as "s" (static) and "e" (ephemeral) to indicate which keys are being exchanged and verified. This token-based approach allows NHP to selectively authenticate one or both parties depending on the specific use case. For example, the "XX" pattern in Noise provides mutual authentication, while the "NK" pattern allows for a one-sided authenticated handshake, giving NHP flexibility in how strictly identity verification is enforced. + +To further protect identity information, Noise can encrypt static keys during the handshake. NHP leverages this feature to prevent an eavesdropper from discovering the identities of the participants, thus supporting the zero-trust model by ensuring that the identity of any participant is revealed only to the intended counterpart and not to third parties. + +### 2.5 Algorithms and Formulas +The cryptographic strength of NHP's integration with the Noise Protocol Framework is built on the use of well-defined algorithms and mathematical formulas. Here, we provide an overview of the key algorithms and their corresponding formulas that are used in NHP for key exchange, encryption, and identity verification. + +#### 2.5.1 Diffie-Hellman Key Exchange +The Diffie-Hellman (DH) key exchange is used to derive a shared secret between two parties, \( A \) and \( B \). Each party generates a private key (\( a \) for \( A \), \( b \) for \( B \)) and computes a public key by exponentiating a common generator \( g \) to their private key in a finite cyclic group of prime order \( p \): + +- \( A \) computes its public key: \( A_{pub} = g^a \mod p \) +- \( B \) computes its public key: \( B_{pub} = g^b \mod p \) + +The shared secret \( s \) is then computed by both parties using the other party's public key: + +- \( A \) computes: \( s = B_{pub}^a \mod p \) +- \( B \) computes: \( s = A_{pub}^b \mod p \) + +The resulting shared secret \( s \) is identical for both parties and is used to derive encryption keys. + +#### 2.5.2 Symmetric Encryption +NHP uses symmetric encryption for data confidentiality. The key \( k \) and nonce \( n \) are used in the encryption function. For authenticated encryption with associated data (AEAD), the ChaCha20-Poly1305 algorithm is commonly used, which combines a stream cipher (ChaCha20) for encryption and a MAC (Poly1305) for authentication. + +- Encryption: \( c = ext{ChaCha20}(k, n, ext{plaintext}) \) +- Authentication: \( ext{tag} = ext{Poly1305}(k, ext{associated data} || c) \) + +The ciphertext \( c \) and the tag are transmitted together, ensuring both confidentiality and integrity. + +#### 2.5.3 Key Derivation and Hashing +Noise uses a key derivation function (KDF) based on HMAC (Hash-based Message Authentication Code) to derive keys. The HKDF (HMAC-based Key Derivation Function) is used to produce multiple keys from the shared secret \( s \). + +- HKDF steps: + - \( ext{temp\_key} = ext{HMAC}( ext{chaining\_key}, ext{input\_key\_material}) \) + - \( ext{output1} = ext{HMAC}( ext{temp\_key}, 0x01) \) + - \( ext{output2} = ext{HMAC}( ext{temp\_key}, ext{output1} || 0x02) \) + +The derived keys are used for encryption and maintaining the chaining key (\( ck \)) that evolves with each message, ensuring forward secrecy. + +#### 2.5.4 Identity Verification +Identity verification in NHP involves using static and ephemeral keys to authenticate parties. The Diffie-Hellman operations between static (\( s \)) and ephemeral (\( e \)) keys produce unique shared values that verify the identity of the participants. + +- For identity verification, a combination of DH operations is performed: + - \( ext{ss} = DH(s_A, s_B) \) + - \( ext{es} = DH(e_A, s_B) \) or \( DH(s_A, e_B) \) + - \( ext{ee} = DH(e_A, e_B) \) + +These values are hashed together to derive the final session key, effectively binding the identities to the key exchange process and ensuring that only the intended parties can derive the correct session key. + +### 2.6 Summary +NHP's implementation of the Noise Protocol Framework strengthens its zero-trust architecture by leveraging robust and well-tested cryptographic mechanisms for key exchange, data encryption, and identity verification. The modular nature of Noise allows NHP to adapt the handshake and encryption process based on the threat model, providing a high level of security against active and passive attacks. By incorporating Noise, NHP can maintain secure and authenticated communication channels while hiding the network infrastructure from attackers, achieving its goal of protecting network resources in hostile environments. + +## 3) Key Distribution and Management + +### 3.1 Introduction + +Certificateless Public Key Cryptography, originally introduced by Al-Riyami and Paterson in 2003, provides a hybrid solution that eliminates the need for a conventional Certificate Authority (CA) while ensuring strong cryptographic assurances. This article explores how NHP utilizes CL-PKC for efficient and secure key management without relying on certificates, which are typically seen as a vulnerability in many cryptographic systems. + +In traditional Public Key Infrastructure (PKI), certificate authorities (CAs) serve as trusted third parties that issue and manage public key certificates to verify the authenticity of users' keys. While effective, this model introduces complexities and risks, such as dependency on the CA and exposure to attacks targeting these central entities. Certificateless Public Key Cryptography aims to mitigate these issues by eliminating the use of certificates while still ensuring the authenticity of public keys. + +In a CL-PKC system, a trusted third party called the Key Generation Center (KGC) is responsible for generating partial private keys for users. However, unlike a CA, the KGC does not have access to the complete private keys, which makes it impossible for the KGC to impersonate users. Each user combines the partial key from the KGC with their own secret value to generate their full private key and public key. This approach reduces the trust placed on any single entity and provides an extra layer of security. + +### 3.2 Advantages of Using CL-PKC in NHP + +1. **Reduced Trust Requirements**: Unlike PKI, where trust in the CA is critical, CL-PKC reduces this trust requirement. The KGC cannot generate complete private keys on its own, meaning it cannot impersonate users or decrypt their communications. + +2. **Simplified Key Distribution**: There is no need for users to request or renew certificates, which eliminates many administrative burdens associated with traditional PKI. + +3. **Resistance to Key Compromise**: Since the user's full private key is generated in part by the user, compromising the KGC does not allow an adversary to fully recover user keys. This mitigates the impact of a successful attack on the key distribution infrastructure. + +4. **Scalability**: The certificateless nature of the system removes the need for managing large certificate databases, which simplifies scalability. This is particularly useful for IoT and other large-scale deployments where the overhead of certificate management would be prohibitive. + +### 3.3 Key Management in NHP Using Certificateless Cryptography + +The NHP Zero Trust protocol integrates CL-PKC to manage the distribution and verification of keys for its secure communication framework. Below, we explain how the various mechanisms of CL-PKC contribute to the key management process in NHP. + +#### 3.3.1 Key Generation and Distribution + +In NHP, the Key Generation Center (KGC) is responsible for creating system-wide parameters, including the master public-private key pair. The master private key is kept confidential by the KGC, while the master public key is distributed to all participants. When a new user wants to join the network, the KGC performs the following steps: + +1. **Partial Key Generation**: The KGC generates a partial private key for the user using their unique identifier (e.g., an email or other identity information). This ensures that each user’s partial key is bound to their identity, providing identity-based security. + +2. **User-Specific Key Pair Generation**: The user then selects their own secret value and combines it with the partial private key from the KGC to generate their full private key. The public key is computed from the combined secret, which means that while the KGC contributes to the key generation, it does not possess the complete private key. + +This key distribution method ensures that the KGC cannot unilaterally determine a user's private key, mitigating the risks associated with compromised key generation authorities. Additionally, the lack of a need for traditional certificates means that users do not need to rely on external certificate authorities to validate keys, reducing the attack surface for man-in-the-middle (MITM) attacks. + +#### 3.3.2 Public Key Verification Without Certificates + +In certificateless systems like the one implemented in NHP, the authenticity of public keys is verified through implicit methods, rather than relying on certificates signed by a CA. Specifically, the user's public key is computed using the system parameters, the user's identifier, and the KGC's master public key. This computation is deterministic and allows any party to verify the authenticity of a public key without needing to trust a CA or store a large database of certificates. + +By removing the need for traditional certificates, NHP is able to streamline the key verification process, eliminating the need for certificate revocation lists (CRLs) and other PKI complexities. This approach not only reduces the communication overhead but also enhances the security by removing dependencies on a trusted third party that could be targeted by attackers. + +### 3.4 Algorithms in CL-PKC for NHP + +To provide a clearer understanding of how the NHP protocol leverages Certificateless Public Key Cryptography, we describe the key algorithms involved along with their respective formulas. + +#### 3.4.1 System Parameter Generation + +The Key Generation Center (KGC) is responsible for generating the system parameters that will be used across the network. These parameters include an elliptic curve \( E \) defined over a finite field \( \mathbb{F}_q \), a base point \( G \) of prime order \( n \), and the master secret key \( ms \). The KGC computes the master public key \( P_{pub} \) as: + +\[ +P_{pub} = [ms]G +\] + +where \( [ms]G \) denotes scalar multiplication of the base point \( G \) by the master secret \( ms \). + +#### 3.4.2 Partial Private Key Generation + +For each user with a unique identifier \( ID_A \), the KGC generates a partial private key. First, the KGC computes a hash value \( H_A \) based on the user's identifier and system parameters: + +\[ +H_A = H(ENTL_A \parallel ID_A \parallel a \parallel b \parallel x_G \parallel y_G \parallel x_{P_{pub}} \parallel y_{P_{pub}}) +\] + +where \( ENTL_A \) is a length value derived from the identifier, and \( (x_G, y_G) \) and \( (x_{P_{pub}}, y_{P_{pub}}) \) are the coordinates of points \( G \) and \( P_{pub} \), respectively. + +The KGC then selects a random value \( w \in [1, n-1] \) and computes: + +\[ +W_A = [w]G + U_A +\] + +where \( U_A = [d'_A]G \) is a point generated by the user with their own secret value \( d'_A \). + +The partial private key \( t_A \) is computed as: + +\[ +t_A = (w + l \cdot ms) \mod n +\] + +where \( l \) is a hash value computed from the point \( W_A \) and the hash \( H_A \). + +#### 3.4.3 User Full Private Key Generation + +The user generates their full private key \( d_A \) by combining the partial private key \( t_A \) with their secret value \( d'_A \): + +\[ +d_A = (t_A + d'_A) \mod n +\] + +This ensures that only the user knows their complete private key. + +#### 3.4.4 Public Key Computation + +The user’s public key \( P_A \) is computed as: + +\[ +P_A = W_A + [l]P_{pub} +\] + +This public key can be verified by anyone using the system parameters, the user’s identifier, and the KGC’s public key. + +#### 3.4.5 Signature Generation and Verification + +To generate a digital signature on a message \( M \), the user computes a hash \( e \) as follows: + +\[ +e = H(H_A \parallel x_{W_A} \parallel y_{W_A} \parallel M) +\] + +The signature \( (r, s) \) is generated using the user’s private key \( d_A \) and a random value \( k \): + +\[ +[r]G = (x_1, y_1) +\] +\[ +r = x_1 \mod n +\] +\[ +s = (k^{-1}(e + d_A \cdot r)) \mod n +\] + +To verify the signature, the verifier computes \( P_A \) and then checks whether: + +\[ +[r]G = [s]G + [e + r]P_A +\] + +If the equality holds, the signature is valid. + +### 3.5 Conclusion + +NHP’s implementation of Certificateless Public Key Cryptography provides a powerful and efficient approach to key management in Zero Trust environments. By leveraging CL-PKC, NHP is able to mitigate the risks associated with traditional PKI, reduce the reliance on centralized trusted authorities, and simplify the key distribution process. The result is a more secure and scalable system that is well-suited for protecting critical network infrastructure in the face of evolving cyber threats. + +The combination of certificateless cryptography and the Zero Trust principles of NHP makes it an attractive solution for securing network resources while minimizing the risks introduced by centralized authorities. + + diff --git a/docs/deploy.md b/docs/deploy.md index d1ec30c6..aaccdd5a 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -1,13 +1,16 @@ --- layout: page title: How to Deploy -nav_order: 4 +nav_order: 5 permalink: /deploy/ --- # Deploy OpenNHP Binaries {: .fs-9 } +OpenNHP is cross-platform software that is easy to deploy. +{: .fs-6 .fw-300 } + [中文版](/zh-cn/deploy/){: .label .fs-4 } --- diff --git a/docs/features.md b/docs/features.md index d9d96e5e..7bf3dcfd 100644 --- a/docs/features.md +++ b/docs/features.md @@ -8,6 +8,9 @@ permalink: /features/ # OpenNHP Feature List {: .fs-9 } +OpenNHP offers robust security, excellent performance, and scalability to protect your network resources. +{: .fs-6 .fw-300 } + [中文版](/zh-cn/features/){: .label .fs-4 } --- diff --git a/docs/images/TrustworthyCyberspace.png b/docs/images/TrustworthyCyberspace.png index 262cd6dc..790f917e 100644 Binary files a/docs/images/TrustworthyCyberspace.png and b/docs/images/TrustworthyCyberspace.png differ diff --git a/docs/images/logo11.png b/docs/images/logo11.png new file mode 100644 index 00000000..8e5cc388 Binary files /dev/null and b/docs/images/logo11.png differ diff --git a/docs/images/logo12.png b/docs/images/logo12.png new file mode 100644 index 00000000..612ff900 Binary files /dev/null and b/docs/images/logo12.png differ diff --git a/docs/images/win10wsl_en.png b/docs/images/win10wsl_en.png new file mode 100644 index 00000000..dc9894d9 Binary files /dev/null and b/docs/images/win10wsl_en.png differ diff --git a/docs/index.md b/docs/index.md index adb41a71..9472e755 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,7 +9,7 @@ permalink: / # OpenNHP Documentation {: .fs-9 } - OpenNHP implements the Zero Trust communication protocol NHP. +A lightweight cryptography-driven zero trust networking protocol at the OSI 5th layer to hide your server and data from attackers. {: .fs-6 .fw-300 } [中文版](/zh-cn/){: .label .fs-4 } diff --git a/docs/server_plugin.md b/docs/server_plugin.md index 00fb8575..b984ede7 100644 --- a/docs/server_plugin.md +++ b/docs/server_plugin.md @@ -1,7 +1,7 @@ --- layout: page title: Server Plugins -nav_order: 11 +nav_order: 8 permalink: /server_plugin/ --- diff --git a/docs/zh-cn/about.zh-cn.md b/docs/zh-cn/about.zh-cn.md index 79ad810d..1289082b 100644 --- a/docs/zh-cn/about.zh-cn.md +++ b/docs/zh-cn/about.zh-cn.md @@ -2,13 +2,14 @@ layout: page title: 关于我们 parent: 中文版 -nav_order: 8 +nav_order: 9 permalink: /zh-cn/about/ --- # 关于OpenNHP开源项目 {: .fs-9 } +[English](/about/){: .label .fs-4 } --- diff --git a/docs/zh-cn/build.zh-cn.md b/docs/zh-cn/build.zh-cn.md index f8c9af49..5b3af554 100644 --- a/docs/zh-cn/build.zh-cn.md +++ b/docs/zh-cn/build.zh-cn.md @@ -2,13 +2,15 @@ layout: page title: 编译源代码 parent: 中文版 -nav_order: 5 +nav_order: 6 permalink: /zh-cn/build/ --- # 编译OpenNHP {: .fs-9 } +[English](/build/){: .label .fs-4 } + --- ## 1. WSL环境准备 @@ -33,16 +35,16 @@ permalink: /zh-cn/build/ - **【WSL环境的IP地址】** 在WSL的Linux环境中,运行以下命令获取IP地址: -| 主机 | 查看IP地址的命令 | -|:--:|:--:| -| WSL中Linux主机 | `hostname -I \| awk '{print $1}'` | -| WSL宿主Windows主机 | `ip route show \| grep -i default \| awk '{ print $3}'` | +| 主机 | 查看IP地址的命令 | +| :----------------: | :-----------------------------------------------------: | +| WSL中Linux主机 | `hostname -I \| awk '{print $1}'` | +| WSL宿主Windows主机 | `ip route show \| grep -i default \| awk '{ print $3}'` | ## 2. 系统需求 -- 2.1 `Go语言`环境:**Go 1.18** 或以上。安装包下载地址: +- 2.1 `Go语言`环境:**Go 1.21** 。安装包下载地址: - **Windows与macOS**环境下,通过下载的安装程序来安装Go。 - - **Linux**环境下可以直接通过管理工具安装: :`sudo apt install golang ` + - **Linux**环境下可以直接通过管理工具安装: `sudo apt install golang ` - 安装成功后,运行命令`go version` 来查看Go版本号。 - **Windows与macOS**环境下,通过下载的安装程序来安装Go。 - **Linux**环境下可以直接通过管理工具安装:`sudo apt install golang` 或者通过以下命令手动安装: @@ -88,7 +90,34 @@ permalink: /zh-cn/build/ ```bat wsl --install --distribution Ubuntu-22.04 ``` - + +*注:如果 2.1 和 2.2 已完成,直接在项目目录下执行编译命令 `.\build.bat` 时,通常会遇到 `系统找不到指定的路径`或 ` 'lib' 不是内部或外部命令,也不是可运行的程序或批处理文件。` 的错误。2.3 提供了解决该问题的方法,供参考使用。* + +- 2.3 `lib`环境: + + + - 在编译运行的命令中使用了 lib 工具,这是用于生成 .lib 文件的工具,通常用于链接静态库或导出符号表(在 Windows 中生成 .lib 文件以便与 .dll 文件配合使用)。遇到的错误提示 lib 不是内部或外部命令,表示系统找不到 lib 工具。 + + - **解决('lib' 不是内部或外部命令,也不是可运行的程序或批处理文件)问题 :** 安装 Visual Studio 和 Visual Studio tools。 + + - lib 工具是微软的库管理工具,通常随 Visual Studio 的 Microsoft Build Tools 安装。确保你已安装 Visual Studio,并且选择了 C++ 生成工具(C++ Build Tools)组件,其中包括 lib.exe。 + + - 如果还没有安装 Visual Studio,可以从 Visual Studio 官方网站下载安装:https://visualstudiomicrosoft.com/zh-hans/ 安装时,选择“桌面开发(C++)”工作负载,它包含 lib.exe 及其他必要的工具。 + + - 安装 Visual Studio 后,确保使用 Visual Studio 开发者命令行(Developer Command Prompt) 来运行包含 lib 命令的 `build.bat `文件。这个命令行工具会自动加载构建工具的环境变量,如 lib.exe + + - **解决(系统找不到指定路径的错误)问题 :** 更改`bulid.bat`文件中的路径 + + - 打开 `build.bat` 文件,找到 + ```bat + call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 + ``` + + - 修改为你自己的 visual studio目录下安装路径。比如: + ```bat + call "F:\develop\visualstu\VC\Auxiliary\Build\vcvarsall.bat" x64 + ``` + ## 3. 编译 1. 拉取代码仓库 @@ -107,7 +136,8 @@ permalink: /zh-cn/build/ - **Linux与macOS**:运行代码根目录下脚本 `make` - **Windows**:运行代码根目录下*BAT*文件 - `build.bat` + `build.bat`
+ *(注:如果在windows下编译过程中出现错误,请尝试此编译方法:在Visual Studio的developer command prompt for VS命令窗口中,切换到项目目录,执行`./build.bat`命令)* ## 4. 结果 diff --git a/docs/zh-cn/code.zh-cn.md b/docs/zh-cn/code.zh-cn.md index 4f3fa409..533edf6a 100644 --- a/docs/zh-cn/code.zh-cn.md +++ b/docs/zh-cn/code.zh-cn.md @@ -2,13 +2,15 @@ layout: page title: 源代码解读 parent: 中文版 -nav_order: 6 +nav_order: 7 permalink: /zh-cn/code/ --- # OpeNHP代码解读 {: .fs-9 } +[English](/code/){: .label .fs-4 } + --- ## 1. 层级架构 diff --git a/docs/zh-cn/comparison.zh-cn.md b/docs/zh-cn/comparison.zh-cn.md index d3be8211..27d5cd03 100644 --- a/docs/zh-cn/comparison.zh-cn.md +++ b/docs/zh-cn/comparison.zh-cn.md @@ -2,17 +2,19 @@ layout: page title: 对比NHP与SPA parent: 中文版 -nav_order: 3 +nav_order: 4 permalink: /zh-cn/comparison/ --- -- {: .fs-9 } +# 对比NHP与SPA +{: .fs-9 } -*       ( 注:以下内容摘自《Applied Sciences》杂志2024年第14卷第13期的期刊论文《AHAC: Advanced Network-Hiding Access Control Framework》论文,值得特别指出的是,AHAC框架是NHP(OpenNHP)技术体系中的一个关键组成部分。本文详细介绍和比较 NHP(Next-Generation Host Protocol)与 SPA(Secure Protocol Architecture)在不同方面的表现。)* +注: 以下内容摘自《Applied Sciences》杂志2024年第14卷第13期的期刊论文《AHAC: Advanced Network-Hiding Access Control Framework》论文,值得特别指出的是,AHAC框架是NHP(OpenNHP)技术体系中的一个关键组成部分。 -# NHP与SPA的对比 +[English](/comparison/){: .label .fs-4 } + +--- -# 目录: - [NHP与SPA的对比](#nhp与spa的对比) - [目录:](#目录) - [1. 优势对比](#1-优势对比) @@ -26,10 +28,9 @@ permalink: /zh-cn/comparison/ - [4.2 与FIDO集成](#42-与fido集成) - [5. 兼容性对比](#5-兼容性对比) - ## 1. 优势对比 -       NHP通过结合噪声协议、密钥对和ECDH算法,提供强大的双向认证机制。与传统方法相比,NHP在性能、可扩展性和安全性上有显著优势。它支持多种编程语言(如C/C++、Python、Java和Go),并提供高度可扩展的架构,增强设备验证能力,防御重放攻击,彻底解决IP放大问题。NHP特别适合企业IAM系统和安全资源访问等需要强认证和加密的场景,优化了性能并提升了高可用性,确保在复杂环境中的无缝兼容性和高安全性。相比之下,SPA虽然具有一定优势,但在安全性、性能和可扩展性方面仍无法与NHP媲美。 +NHP通过结合噪声协议、密钥对和ECDH算法,提供强大的双向认证机制。与传统方法相比,NHP在性能、可扩展性和安全性上有显著优势。它支持多种编程语言(如C/C++、Python、Java和Go),并提供高度可扩展的架构,增强设备验证能力,防御重放攻击,彻底解决IP放大问题。NHP特别适合企业IAM系统和安全资源访问等需要强认证和加密的场景,优化了性能并提升了高可用性,确保在复杂环境中的无缝兼容性和高安全性。相比之下,SPA虽然具有一定优势,但在安全性、性能和可扩展性方面仍无法与NHP媲美。 | | SPA | NHP | | ------------ | ------------------------------ | ------------------------------ | @@ -50,7 +51,7 @@ permalink: /zh-cn/comparison/ ### 2.1 加密算法开销 -       SPA 采用的是 RSA 加密算法,而 NHP 采用的是 ECC 加密算法。我们根据安全强度和密钥长度比较了 RSA 和 ECC 的性价比,如下表所示。在相同的安全标准下,ECC 算法的密钥长度显著短于 RSA 算法。此外,RSA 消息签名生成的密文大小大致等于密钥长度。因此,在验证网络消息身份时,NHP 使用更短的 ECC 随机密钥(32 字节或 64 字节)进行 ECDH 交换,而不是传输较大的 RSA2048 消息签名(256 字节)进行验证,不仅降低了计算开销,还更加高效地节省了宝贵的带宽资源。这一策略表明,NHP 在提高系统效率和资源利用率方面相较于 SPA 具有显著优势。 +SPA 采用的是 RSA 加密算法,而 NHP 采用的是 ECC 加密算法。我们根据安全强度和密钥长度比较了 RSA 和 ECC 的性价比,如下表所示。在相同的安全标准下,ECC 算法的密钥长度显著短于 RSA 算法。此外,RSA 消息签名生成的密文大小大致等于密钥长度。因此,在验证网络消息身份时,NHP 使用更短的 ECC 随机密钥(32 字节或 64 字节)进行 ECDH 交换,而不是传输较大的 RSA2048 消息签名(256 字节)进行验证,不仅降低了计算开销,还更加高效地节省了宝贵的带宽资源。这一策略表明,NHP 在提高系统效率和资源利用率方面相较于 SPA 具有显著优势。 | 安全强度(比特) | SPA (最小值公钥长度(位)) | NHP(最小值公钥长度(位)) | NHP vs SPA(密钥长度比) | 有效期 | @@ -62,7 +63,7 @@ permalink: /zh-cn/comparison/ | 256 | 15360 | 512+ | 1:30 | | -       我们通过实验测量了 RSA 和 ECC 的加解密时间,具体结果见下表。实验增加了加密和解密的循环次数,测试两种算法在不同情况下的性能。结果显示,尽管 RSA 和 ECC 的加解密时间随着循环次数的增加而上升,但 ECC 的时间开销始终远低于 RSA。尤其在循环次数增多时,ECC 的优势更加明显,RSA 的时间开销最高达到 ECC 的约 800 倍。这一显著差距表明,NHP 在加解密效率上明显优于 SPA,为实际应用中选择更高效的加密算法提供了有力的依据。 +我们通过实验测量了 RSA 和 ECC 的加解密时间,具体结果见下表。实验增加了加密和解密的循环次数,测试两种算法在不同情况下的性能。结果显示,尽管 RSA 和 ECC 的加解密时间随着循环次数的增加而上升,但 ECC 的时间开销始终远低于 RSA。尤其在循环次数增多时,ECC 的优势更加明显,RSA 的时间开销最高达到 ECC 的约 800 倍。这一显著差距表明,NHP 在加解密效率上明显优于 SPA,为实际应用中选择更高效的加密算法提供了有力的依据。 | 循环次数(次) | SPA | NHP | | -------------- | -------- | ------ | @@ -77,36 +78,34 @@ permalink: /zh-cn/comparison/ ### 2.2 性能开销 -       为了全面评估 NHP 的性能表现,我们搭建了一个下图所示的实验环境,针对 NHP 和 SPA 进行了负载性能测试。该环境由两个主要区域组成:Agent 部署区域和网络隐身部署区域。 +为了全面评估 NHP 的性能表现,我们搭建了一个下图所示的实验环境,针对 NHP 和 SPA 进行了负载性能测试。该环境由两个主要区域组成:Agent 部署区域和网络隐身部署区域。 ![部署图](/images/Deploment_diagram.png) -       在网络隐身部署区域,我们集成了网络隐身服务器和应用服务器作为关键组件。为了确保测试环境的稳定性和一致性,我们选用了三台配置相同的机器,每台配备 4 核 CPU 和 8G 内存。在 agent 部署区域,我们启动了 n 个 agent 服务,这些服务以每秒发送一次敲门请求的频率与网络隐身服务器通信。同时,在网络隐身服务器上部署了 JMeter 组件,用于模拟和监控其性能表现。应用服务器端同样部署了 JMeter 服务,实时跟踪网络隐身服务器的性能资源消耗情况。通过这种设置,我们能够全面监控和比较 NHP 与 SPA 的性能表现。 +在网络隐身部署区域,我们集成了网络隐身服务器和应用服务器作为关键组件。为了确保测试环境的稳定性和一致性,我们选用了三台配置相同的机器,每台配备 4 核 CPU 和 8G 内存。在 agent 部署区域,我们启动了 n 个 agent 服务,这些服务以每秒发送一次敲门请求的频率与网络隐身服务器通信。同时,在网络隐身服务器上部署了 JMeter 组件,用于模拟和监控其性能表现。应用服务器端同样部署了 JMeter 服务,实时跟踪网络隐身服务器的性能资源消耗情况。通过这种设置,我们能够全面监控和比较 NHP 与 SPA 的性能表现。 -       在保持实验环境一致性的前提下,我们按照部署方案分别选取了1、10、20、30、40、50个agent,对NHP和SPA进行了性能测试。测试结果如表4所示,其中横轴表示参与实验的agent数量,纵轴则显示测试期间的CPU占用率变化。通过这种设置,我们能够直观地观察到随着agent数量的增加,NHP和SPA在CPU资源消耗方面的不同表现。 +在保持实验环境一致性的前提下,我们按照部署方案分别选取了1、10、20、30、40、50个agent,对NHP和SPA进行了性能测试。测试结果如表4所示,其中横轴表示参与实验的agent数量,纵轴则显示测试期间的CPU占用率变化。通过这种设置,我们能够直观地观察到随着agent数量的增加,NHP和SPA在CPU资源消耗方面的不同表现。 ![CPU对比](/images/CPU_compare.png) -       实验结果显示,随着 Agent 数量的增加,NHP 和 SPA 的 CPU 负载均呈现上升趋势。然而,随着 Agent 数量的进一步增加,NHP 的性能优势逐渐凸显,其 CPU 负载大约维持在 SPA 的一半左右,展现出显著的效率提升。
-*       ( 注:尽管理论上 NHP 的性能应较 SPA 提升约 1000 倍,但实际测试中仅提升约 1 倍。分析原因,主要因素包括网络开销对性能的显著影响、垃圾回收机制导致的性能损失,以及硬件环境差异。此外,尽管出于代码安全性和加密算法实现的考虑,我们选择了内存安全的 Go 语言开发,但其垃圾回收机制也对性能产生了一定影响。)* -## +实验结果显示,随着 Agent 数量的增加,NHP 和 SPA 的 CPU 负载均呈现上升趋势。然而,随着 Agent 数量的进一步增加,NHP 的性能优势逐渐凸显,其 CPU 负载大约维持在 SPA 的一半左右,展现出显著的效率提升。 +> 注:尽管理论上 NHP 的性能应较 SPA 提升约 1000 倍,但实际测试中仅提升约 1 倍。分析原因,主要因素包括网络开销对性能的显著影响、垃圾回收机制导致的性能损失,以及硬件环境差异。此外,尽管出于代码安全性和加密算法实现的考虑,我们选择了内存安全的 Go 语言开发,但其垃圾回收机制也对性能产生了一定影响。 ## 3. 高可用性对比 -       NHP 通过分布式架构实现零信任服务的高可用性,确保敲门模块和门禁模块在不同主机上部署,以避免资源占用和提升弹性扩展。即使发生故障,也能无缝切换服务,维持系统功能和响应速度。这种设计增强了系统的稳健性和稳定性,降低了服务故障对整体系统的影响,如下图所示。 +NHP 通过分布式架构实现零信任服务的高可用性,确保敲门模块和门禁模块在不同主机上部署,以避免资源占用和提升弹性扩展。即使发生故障,也能无缝切换服务,维持系统功能和响应速度。这种设计增强了系统的稳健性和稳定性,降低了服务故障对整体系统的影响,如下图所示。 ![高可用架构](/images/High-availability.png) -       NHP 支持敲门验证服务的横向弹性扩展,能够根据实时负载动态调整服务实例数。这一功能提供了极高的弹性和可扩展性,确保在高负载下服务依然快速响应且稳定。每个服务实例均能处理敲门请求并维持业务会话,这种设计不仅提升了处理能力,还增强了容错性,保证了业务连续性和稳定性。从测试结果来看,NHP 在高可用性方面相较于 SPA 显著提升 +NHP 支持敲门验证服务的横向弹性扩展,能够根据实时负载动态调整服务实例数。这一功能提供了极高的弹性和可扩展性,确保在高负载下服务依然快速响应且稳定。每个服务实例均能处理敲门请求并维持业务会话,这种设计不仅提升了处理能力,还增强了容错性,保证了业务连续性和稳定性。从测试结果来看,NHP 在高可用性方面相较于 SPA 显著提升 ![负载图](/images/Load_diagram.png) - ## 4. 扩展性对比 -       尽管NHP设计为数据通信提供了可信、可控、可靠和可证的基础保障,但考虑到通信场景和环境的多样性和复杂性,NHP还需具备良好的扩展性以适应不同的定制需求。NHP的扩展性体现在几个方面: +尽管NHP设计为数据通信提供了可信、可控、可靠和可证的基础保障,但考虑到通信场景和环境的多样性和复杂性,NHP还需具备良好的扩展性以适应不同的定制需求。NHP的扩展性体现在几个方面: - 其双向通信机制相比SPA的单向敲门机制,提供了更丰富的扩展能力,可以隐藏资源的真实IP地址并支持数据通信前后的密钥交换,增强隐私计算和数据流通场景的安全性。 @@ -114,11 +113,11 @@ permalink: /zh-cn/comparison/ - NHP的资源标识支持任意字符串形式,包括中英文及符号,为数据资源提供更强的描述性,并具备DNS解析功能,提供更安全、加密和隐私的域名解析服务。 -       因此,NHP的扩展性架构涵盖了与DNS和FIDO的集成等典型应用场景。 +因此,NHP的扩展性架构涵盖了与DNS和FIDO的集成等典型应用场景。 ### 4.1 与DNS集成 -       DNS作为互联网基础服务在网站运行中至关重要,但其安全性长期未被重视,且因使用不可靠的UDP协议,存在诸多安全漏洞,如DNS劫持和拒绝服务攻击。因此,加强DNS安全至关重要。通过集成网络隐身技术,DNS解析通过双向加密通道进行,确保了保密性和防篡改能力,同时只有经过身份认证的用户才能解析,从而有效防御DDoS攻击和劫持。具体实现方案如下图所示,我们的方法能够显著提升了DNS的安全性,为用户提供了更可靠的DNS服务。 +DNS作为互联网基础服务在网站运行中至关重要,但其安全性长期未被重视,且因使用不可靠的UDP协议,存在诸多安全漏洞,如DNS劫持和拒绝服务攻击。因此,加强DNS安全至关重要。通过集成网络隐身技术,DNS解析通过双向加密通道进行,确保了保密性和防篡改能力,同时只有经过身份认证的用户才能解析,从而有效防御DDoS攻击和劫持。具体实现方案如下图所示,我们的方法能够显著提升了DNS的安全性,为用户提供了更可靠的DNS服务。 ![DNS集成方案](/images/DNS_integration.png) @@ -138,7 +137,7 @@ permalink: /zh-cn/comparison/ ### 4.2 与FIDO集成 -       尽管FIDO在Web身份认证方面表现出色,但服务器的潜在漏洞仍可能被黑客利用,从而绕过FIDO的认证,直接入侵服务器进行数据盗窃或破坏。将FIDO与NHP集成,可以有效弥补FIDO在漏洞防护方面的不足,为互联网暴露面提供更全面的防御方案。具体实现方案如下图所示,详细实现步骤如下。 +尽管FIDO在Web身份认证方面表现出色,但服务器的潜在漏洞仍可能被黑客利用,从而绕过FIDO的认证,直接入侵服务器进行数据盗窃或破坏。将FIDO与NHP集成,可以有效弥补FIDO在漏洞防护方面的不足,为互联网暴露面提供更全面的防御方案。具体实现方案如下图所示,详细实现步骤如下。 ![FIDO集成方案](/images/FIDO_integration.png) @@ -168,7 +167,7 @@ permalink: /zh-cn/comparison/ ## 5. 兼容性对比 -       与SPA协议相比,NHP的一个关键目标是对信创环境以及国内零信任标准体系的良好兼容性。在加密算法方面,NHP支持国际密码算法(如RSA、SHA256、AES)和国密算法(如SM2、SM3、SM4),并能根据数据包头的长度调整加密时间。在软硬件兼容性方面,NHP适配了国内外主流的CPU硬件和操作系统,包括鲲鹏、x86、龙芯、申威等。此外,NHP符合即将颁布的国家标准《信息安全技术零信任参考体系架构》的规范要求,确保了与该标准的兼容性,如下图所示。 +与SPA协议相比,NHP的一个关键目标是对信创环境以及国内零信任标准体系的良好兼容性。在加密算法方面,NHP支持国际密码算法(如RSA、SHA256、AES)和国密算法(如SM2、SM3、SM4),并能根据数据包头的长度调整加密时间。在软硬件兼容性方面,NHP适配了国内外主流的CPU硬件和操作系统,包括鲲鹏、x86、龙芯、申威等。此外,NHP符合即将颁布的国家标准《信息安全技术零信任参考体系架构》的规范要求,确保了与该标准的兼容性,如下图所示。 ![兼容性对比](/images/Compatibility_comparison.png) diff --git a/docs/zh-cn/cryptography.zh-cn.md b/docs/zh-cn/cryptography.zh-cn.md new file mode 100644 index 00000000..6acd429b --- /dev/null +++ b/docs/zh-cn/cryptography.zh-cn.md @@ -0,0 +1,286 @@ +--- +layout: page +title: 加密算法 +parent: 中文版 +nav_order: 3 +permalink: /zh-cn/cryptography/ +--- + +# 加密算法 +{: .fs-9 } + +加密是 OpenNHP 的核心,通过利用尖端的加密算法提供强大的安全性、卓越的性能和可扩展性。 +{: .fs-6 .fw-300 } + +[English](/cryptography/){: .label .fs-4 } + +--- + +本文解释了 OpenNHP 如何在多个关键领域中利用现代加密算法的优势: + +1. [公私钥加密算法](#1-%E5%85%AC%E7%A7%81%E9%92%A5%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95) +2. [密钥交换、数据加密和身份认证](#2%E5%AF%86%E9%92%A5%E4%BA%A4%E6%8D%A2%E6%95%B0%E6%8D%AE%E5%8A%A0%E5%AF%86%E5%92%8C%E8%BA%AB%E4%BB%BD%E8%AE%A4%E8%AF%81) +3. [密钥分发和管理](#3-%E5%AF%86%E9%92%A5%E7%AE%A1%E7%90%86%E4%B8%8E%E5%88%86%E5%8F%91) + + +## 1) 公私钥加密算法 +### 1.1 简介 +在不断变化的网络安全环境中,保护通信和网络资源至关重要,特别是在网络威胁日益复杂的情况下。网络基础设施隐藏协议(NHP),作为一种零信任安全机制,致力于通过隐藏网络基础设施细节来防止攻击者入侵,并确保只有受信任的实体能够与网络资源交互。NHP 安全模型的关键组件是椭圆曲线加密(ECC)用于公钥加密。在本文中,我们将探讨 ECC 如何集成到 NHP 零信任协议中,以提供强大且高效的安全性。 + +椭圆曲线加密(ECC)是一种现代公钥加密方法,相较于传统方法如 RSA,它能在显著更小的密钥尺寸下提供相同级别的安全性。ECC 依赖于有限域上椭圆曲线的数学特性,提供了安全性与性能之间的良好平衡。由于其降低了计算开销,ECC 特别适用于资源受限的环境,如嵌入式系统或移动设备。公钥加密是基于非对称密钥对的加密方法,通常包括一个公开的公钥和一个私密的私钥。通过公钥加密,可以实现安全的数据传输、身份验证和数字签名等功能,从而确保通信双方的数据安全性和身份真实性。 + +### 1.2 什么是椭圆曲线加密? + +椭圆曲线加密(ECC)是一种基于椭圆曲线数学理论的公钥加密技术。它通过椭圆曲线方程上的点运算,提供与传统方法(如 RSA)相同的安全性,但密钥尺寸更小,计算效率更高。ECC 的安全性源于椭圆曲线离散对数问题,其计算复杂度使得破解难度极大。 + +在 ECC 中,参与方通过生成公私钥对,利用椭圆曲线 Diffie-Hellman(ECDH)协议来交换密钥。ECC 的数学基础是使用有限域上的椭圆曲线方程: + +y^2 = x^3 + ax + b + +其中 a 和 b 是定义曲线特性的常数,曲线上的点集具有特定的加法运算规则。 + +ECC 的主要优势包括: +- **更小的密钥尺寸**:ECC 能在更小的密钥长度下实现相同的安全性,从而降低了存储和计算的负担。 +- **增强的安全性**:ECC 基于椭圆曲线离散对数问题,其数学复杂性使其非常难以破解。 +- **高效性**:相比传统加密方法如 RSA,ECC 的加密和解密操作速度更快,适用于资源受限的设备(如嵌入式系统和移动设备)。 + +NHP 使用 ECC 进行密钥交换、数据加密和身份验证,以及通过无证书公钥加密(CL-PKC)进行密钥分发和管理。 + +### 1.3 安全通信中的 ECC + +#### 1.3.1 密钥交换机制 + +加密密钥的安全交换是任何安全通信协议的核心。NHP 使用椭圆曲线 Diffie-Hellman(ECDH)作为其密钥交换机制。在 ECDH 密钥交换中,通信双方使用椭圆曲线生成公私密钥对,然后交换公钥,以便双方计算出共享密钥,而无需直接在网络上传递。 + +使用 ECDH 的好处有两个方面:首先,它提供了前向安全性,即使在未来某一方的私钥被泄露,先前建立的会话密钥仍然是安全的。其次,由于 ECC 的高效性,密钥交换过程计算负担较轻,确保密钥建立过程快速完成且计算成本低。 + +#### 1.3.2 数字签名身份验证 + +在零信任环境中,身份验证至关重要。NHP 使用椭圆曲线数字签名算法(ECDSA)来验证试图访问网络资源的实体的身份。ECDSA 是一种基于 ECC 的数字签名方案,它允许设备在不暴露敏感私钥的情况下证明其身份。 + +在 NHP 协议中,当某个实体希望与网络通信时,它必须使用其私钥生成数字签名,接收方可以使用相应的公钥来验证签名的有效性。这确保了只有合法的实体可以参与网络,从而有效地实施零信任模型的 "永不信任,始终验证" 原则。 + +#### 1.3.3 数据保密加密 + +NHP 在通信过程中使用对称加密来确保数据的保密性,但对称密钥必须在实体之间安全地分发和共享。ECC 通过 ECDH 提供安全的对称密钥分发渠道,确保对称密钥可以安全地交换。 + +一旦这些密钥交换完成,NHP 就会切换到对称加密进行数据传输,从中受益于对称加密算法的速度和效率。ECC 确保对称密钥交换既安全又高效。 + +#### 1.3.4 无证书公钥加密中的密钥管理与分发 + +NHP 还通过无证书公钥加密(CL-PKC)使用 ECC 进行密钥管理和分发。在传统的公钥基础设施中,证书被用来验证公钥,这在证书管理方面引入了复杂性。CL-PKC 通过允许实体与受信任的中心合作生成部分私钥,同时独立生成自己的密钥对,从而消除了证书的需要。 + +这种方法简化了密钥管理,确保公钥可以在没有证书颁发和验证的情况下安全使用。通过在 CL-PKC 中使用 ECC,NHP 提供了一种轻量级且安全的密钥分发方式,通过消除对集中式证书机构的依赖,进一步增强了零信任模型。 + +### 1.4 使用 ECC 的优势 + +NHP 零信任协议中使用 ECC 提供了众多符合其安全目标的优势: + +- **可扩展安全性**:ECC 较小的密钥尺寸提供了强大的安全性,能很好地适应对抗对手计算能力不断增强的情况。随着 NHP 致力于为多样化的网络部署提供零信任环境,ECC 的可扩展性是一项关键资产。 +- **资源效率**:相比传统公钥加密,ECC 减少了网络设备的计算负担。在网络资源可能受限的环境中——如边缘设备或物联网组件——这种高效性对于在不牺牲安全性的情况下保持高性能至关重要。 +- **性能提升**:结合 ECDH 的密钥交换、ECDSA 的身份验证以及高效的对称加密提供了一个平衡的安全通信解决方案。这种平衡的方法使得 NHP 能够实现零信任的目标,同时保持较低的延迟,这对时间敏感的网络应用尤为关键。 + +### 1.5 结论 + +将椭圆曲线加密集成到 NHP 零信任协议中,提供了一种在最小性能影响下保护网络通信的强大手段。通过利用 ECDH 进行安全的密钥交换、ECDSA 进行可靠的身份验证,以及高效的对称加密进行数据传输,ECC 支持零信任模型的目标,即隐藏网络基础设施,确保只有受信任的实体可以访问资源,并以低开销保持安全性。 + +随着网络威胁变得越来越复杂,像 ECC 这样的先进加密技术在 NHP 协议中的应用对于保持对攻击者的优势至关重要。ECC 和 NHP 之间的协同作用不仅有助于保护关键的网络基础设施,还确保安全措施既强大又高效——这是任何现代网络安全项目成功的关键组合。 + +网络基础设施隐藏协议(NHP)基于零信任安全模型构建,确保即使在潜在攻击者的存在下也能实现安全通信。为此,NHP 集成了 Noise 协议框架,这是一个用于安全且灵活的密钥交换、数据加密和身份验证的加密框架。该组合以最小的计算开销提供了强大的安全性。 + +## 2)密钥交换、数据加密和身份认证 + +### 2.1 简介 + +#### 2.1.1. 密钥交换机制 + +NHP 利用 Noise 协议的密钥交换机制来确保通信双方之间的安全认证通道。密钥交换从握手阶段开始,双方交换 Diffie-Hellman (DH) 公钥。在 Noise 中,每一方生成一个临时密钥对,并使用交换的公钥派生出共享密钥,该共享密钥随后用于加密后续通信。 + +Noise 允许 NHP 支持长期静态密钥和临时密钥以增强安全性。Noise 框架的握手模式的灵活性使得 NHP 能够根据特定使用场景定制握手过程,提供相互认证、匿名发起者或初始握手本身加密的选项。通过利用 Noise 的基于令牌的握手系统,NHP 可以精确控制密钥交换消息的顺序,同时保持身份信息的机密性。 + +#### 2.1.2 数据加密 + +共享密钥在握手期间派生出来后,Noise 框架使用对称加密来保护数据。NHP 利用 Noise 的 CipherState 和 SymmetricState 对象,这些是 Noise 状态机的核心组件,用于管理通信会话的加密和解密密钥。 + +特别地,握手期间派生的共享密钥用于初始化对称加密密钥(k)和随机数(n),用于数据加密。Noise 支持高级加密方案,如 ChaCha20-Poly1305 或 AES-GCM,提供带有附加数据认证加密(AEAD),以维护数据的保密性和完整性。链式密钥(ck)和握手散列(h)用于在会话过程中不断派生新的密钥,增强前向安全性,确保一个密钥的泄露不会危及通信的其他部分。 + +NHP 通过这些加密属性提供网络数据的加密通道,确保任何被拦截的数据在没有派生密钥的情况下无法被解密。 + +#### 2.1.3 身份验证 + +Noise 通过将静态密钥的交换与 Diffie-Hellman 操作相结合来实现身份验证。在 NHP 中,身份验证发生在握手期间,其中静态密钥被加密并通过共享的 DH 操作进行验证,有效地将双方的公钥绑定到派生的会话密钥上。 + +在握手过程中,Noise 使用诸如 "s"(静态)和 "e"(临时)等令牌来指示正在交换和验证哪些密钥。这种基于令牌的方法使得 NHP 能够根据具体的使用场景选择性地认证单方或双方。例如,Noise 中的 "XX" 模式提供相互认证,而 "NK" 模式允许单方认证的握手,赋予 NHP 在身份验证严格性方面的灵活性。 + +为了进一步保护身份信息,Noise 可以在握手期间加密静态密钥。NHP 利用这一特性来防止窃听者发现参与者的身份,从而支持零信任模型,确保参与者的身份只对预期的对方可见,而不对第三方泄露。 + +### 2.2 算法和公式 + +#### 2.2.1 Diffie-Hellman 密钥交换 + +Diffie-Hellman(DH)密钥交换用于在两方之间派生共享密钥,每一方生成一个私钥(a 对于 A,b 对于 B)并计算公共密钥: + +- A 计算其公钥:A_pub = g^a mod p +- B 计算其公钥:B_pub = g^b mod p + +共享密钥 s 通过以下方式计算: + +- A 计算:s = B_pub^a mod p +- B 计算:s = A_pub^b mod p + +该共享密钥 s 对于双方是相同的,用于派生加密密钥。 + +#### 2.2.2 对称加密 + +NHP 使用对称加密确保数据机密性。密钥(k)和随机数(n)用于加密函数。对于带有附加数据认证加密(AEAD),通常使用 ChaCha20-Poly1305 算法,该算法结合了流密码(ChaCha20)和消息验证码(Poly1305)。 + +- 加密:c = ChaCha20(k, n, plaintext) +- 认证:tag = Poly1305(k, associated data || c) + +密文(c)和标签一起传输,确保数据的机密性和完整性。 + +#### 2.2.3 密钥派生和散列 + +Noise 使用基于 HMAC 的密钥派生函数(KDF)来派生密钥。HKDF(基于 HMAC 的密钥派生函数)用于从共享密钥(s)生成多个密钥。 + +HKDF 步骤: + +- temp_key = HMAC(chaining_key, input_key_material) +- output1 = HMAC(temp_key, 0x01) +- output2 = HMAC(temp_key, output1 || 0x02) + +派生的密钥用于加密和维护链式密钥(ck),以确保前向安全性。 + +#### 2.2.4 身份认证 + +身份验证在 NHP 中涉及静态和临时密钥的交换,以验证参与方。静态(s)和临时(e)密钥之间的 Diffie-Hellman 操作产生唯一的共享值,用于验证参与方的身份。 + +身份验证操作包括: + +- ss = DH(s_A, s_B) +- es = DH(e_A, s_B) 或 DH(s_A, e_B) +- ee = DH(e_A, e_B) + +这些值通过散列结合起来派生最终的会话密钥,有效地将身份绑定到密钥交换过程中,确保只有预期的参与方能够派生出正确的会话密钥。 + +### 2.3 结论 + +Noise 协议框架为 NHP 提供了灵活且强大的加密机制,确保了通信的安全性和参与方身份的验证。通过支持多种握手模式和加密方案,Noise 框架增强了网络的前向安全性和身份保护能力,有效支持了零信任环境下的安全通信需求。结合 Diffie-Hellman 密钥交换、对称加密和基于令牌的身份验证,Noise 框架为 NHP 提供了实现其零信任目标的强大工具。 + +## 3) 密钥管理与分发 + +### 3.1 简介 + +无证书公钥加密(Certificateless Public Key Cryptography,CL-PKC),最初由 Al-Riyami 和 Paterson 在 2003 年提出,提供了一种混合解决方案,在不依赖传统证书机构(CA)的情况下确保强加密保证。本文探讨了 NHP 如何利用 CL-PKC 在不依赖证书的情况下实现高效且安全的密钥管理。 + +在传统的公钥基础设施(PKI)中,证书机构(CAs)作为可信的第三方,负责签发和管理公钥证书以验证用户密钥的真实性。虽然这种模型有效,但它引入了复杂性和风险,例如对 CA 的依赖以及 CA 受到攻击时的风险。无证书公钥加密旨在通过消除证书的使用来缓解这些问题,同时确保公钥的真实性。 + +在 CL-PKC 系统中,一个称为密钥生成中心(KGC)的可信第三方负责为用户生成部分私钥。然而,与 CA 不同的是,KGC 无法访问完整的私钥,因此不可能冒充用户。每个用户将 KGC 提供的部分私钥与他们自己的秘密值结合起来,生成完整的私钥和公钥。这种方法减少了对任何单一实体的信任,并提供了额外的安全层。 + +NHP 零信任协议集成了 CL-PKC 来管理其安全通信框架的密钥分发和验证。下面,我们解释 CL-PKC 的各种机制如何为 NHP 的密钥管理过程做出贡献。 + +#### 3.1.1. 部分密钥生成 + +KGC 负责创建系统范围的参数,包括主公私钥对。主私钥由 KGC 保密,而主公钥则分发给所有参与者。当新用户希望加入网络时,KGC 执行以下步骤: + +- 生成用户的部分私钥,使用他们的唯一标识符(例如电子邮件或其他身份信息)。这确保每个用户的部分密钥与其身份绑定,提供基于身份的安全性。 + +##### 3.1.2 用户特定密钥对生成 + +用户随后选择自己的秘密值,并将其与 KGC 提供的部分私钥结合起来,生成完整的私钥。具体步骤如下: + +- 用户选择一个秘密值 (d'\_A),并使用它来生成一个点 U\_A: + + U\_A = [d'\_A]G + +- 用户将 KGC 提供的部分私钥 (t\_A) 与自己的秘密值 (d'\_A) 相结合,计算出完整的私钥 (d\_A): + + d\_A = (t\_A + d'\_A) mod n + +- 用户使用完整的私钥生成相应的公钥 (P\_A): + + P\_A = W\_A + [l]P\_pub + +用户随后选择自己的秘密值,并将其与 KGC 提供的部分私钥结合起来,生成完整的私钥。用户的公钥也相应生成。 + +这种密钥分发方法确保 KGC 无法单方面确定用户的私钥,从而降低了密钥生成中心被攻破的风险。此外,不需要传统证书意味着用户无需依赖外部证书机构来验证密钥,从而减少了中间人(MITM)攻击的攻击面。 + +在像 NHP 实施的这种无证书系统中,公钥的真实性通过隐式的方法而不是依赖 CA 签发的证书来验证。具体来说,用户的公钥是使用系统参数、用户标识符和 KGC 的主公钥计算出来的。该计算是确定性的,允许任何一方在无需信任 CA 或存储大量证书数据库的情况下验证公钥的真实性。 + +通过消除传统证书的需要,NHP 能够简化密钥验证过程,消除证书吊销列表(CRLs)和其他 PKI 复杂性。这种方法不仅减少了通信开销,还通过消除对可信第三方的依赖增强了安全性,因为这些第三方可能成为攻击者的目标。 + + +### 3.2 算法和公式 + +#### 3.2.1 系统参数生成 + +密钥生成中心(KGC)负责生成系统参数,这些参数包括椭圆曲线 (E) 定义在有限域 (ℚ_q) 上,基点 (G) 的素数阶 (n),以及主私钥 (ms)。KGC 计算主公钥 (P_pub) 如下: + +P_pub = [ms]G + +其中 [ms]G 表示基点 G 与主私钥 ms 的标量乘法。 + +#### 3.2.2 部分私钥生成 + +对于每个用户(标识符为 ID_A),KGC 生成部分私钥。首先,KGC 根据用户的标识符和系统参数计算哈希值 (H_A): + +H_A = H(ENTL_A || ID_A || a || b || x_G || y_G || x_P_pub || y_P_pub) + +其中 (ENTL_A) 是由标识符派生的长度值,(x_G, y_G) 和 (x_P_pub, y_P_pub) 分别是点 G 和 P_pub 的坐标。 + +KGC 选择一个随机值 (w ∈ [1, n-1]) 并计算: + +W_A = [w]G + U_A + +其中 (U_A = [d'_A]G) 是用户使用其自身的秘密值 (d'_A) 生成的点。 + +部分私钥 (t_A) 计算如下: + +t_A = (w + l · ms) mod n + +其中 (l) 是根据点 (W_A) 和哈希 (H_A) 计算的值。 + +#### 3.2.3 用户完整私钥生成 + +用户通过将部分私钥 (t_A) 与其秘密值 (d'_A) 结合生成完整的私钥: + +d_A = (t_A + d'_A) mod n + +这确保只有用户自己知道其完整的私钥。 + +#### 3.2.4 公钥计算 + +用户的公钥 (P_A) 计算如下: + +P_A = W_A + [l]P_pub + +任何人可以使用系统参数、用户标识符和 KGC 的公钥验证该公钥。 + +#### 3.2.5 签名生成与验证 + +用户对消息 (M) 生成数字签名时,计算哈希 (e) 如下: + +e = H(H_A || x_W_A || y_W_A || M) + +签名 (r, s) 使用用户私钥 (d_A) 和一个随机值 (k) 生成: + +[r]G = (x_1, y_1) +r = x_1 mod n +s = (k^{-1}(e + d_A · r)) mod n + +验证签名时,验证者计算 (P_A),然后检查是否满足: + +[r]G = [s]G + [e + r]P_A + +如果等式成立,签名即为有效。 + + +### 3.3 结论 + +NHP 实施的无证书公钥加密为零信任环境中的密钥管理提供了一种强大且高效的方法。通过利用 CL-PKC,NHP 能够缓解传统 PKI 相关的风险,减少对集中可信机构的依赖,并简化密钥分发过程。结果是一个更安全且可扩展的系统,适合在面对不断发展的网络威胁时保护关键网络基础设施。 + +将无证书加密与 NHP 的零信任原则相结合,使其成为在最小化集中机构引入的风险的同时保护网络资源的理想解决方案。 + +Copyright © 2024 OpenNHP Open Source Project. + + diff --git a/docs/zh-cn/deploy.zh-cn.md b/docs/zh-cn/deploy.zh-cn.md index 4f6d19b8..b0118f78 100644 --- a/docs/zh-cn/deploy.zh-cn.md +++ b/docs/zh-cn/deploy.zh-cn.md @@ -2,13 +2,15 @@ layout: page title: 部署OpenNHP parent: 中文版 -nav_order: 4 +nav_order: 5 permalink: /zh-cn/deploy/ --- # 部署OpenNHP {: .fs-9 } +[English](/deploy/){: .label .fs-4 } + --- ## 1. OpenNHP组件说明 diff --git a/docs/zh-cn/features.zh-cn.md b/docs/zh-cn/features.zh-cn.md index 65a63098..6efe0883 100644 --- a/docs/zh-cn/features.zh-cn.md +++ b/docs/zh-cn/features.zh-cn.md @@ -9,6 +9,8 @@ permalink: /zh-cn/features/ # OpenNHP功能列表 {: .fs-9 } +[English](/features/){: .label .fs-4 } + --- diff --git a/docs/zh-cn/index.zh-cn.md b/docs/zh-cn/index.zh-cn.md index bbd1f56d..6a161313 100644 --- a/docs/zh-cn/index.zh-cn.md +++ b/docs/zh-cn/index.zh-cn.md @@ -10,6 +10,6 @@ permalink: /zh-cn/ # OpenNHP中文版文档 {: .fs-9 } -[English Version](/){: .label .fs-4 } - +[English](/){: .label .fs-4 } +--- diff --git a/docs/zh-cn/overview.zh-cn.md b/docs/zh-cn/overview.zh-cn.md index 60394761..12d0b2b8 100644 --- a/docs/zh-cn/overview.zh-cn.md +++ b/docs/zh-cn/overview.zh-cn.md @@ -10,6 +10,8 @@ permalink: /zh-cn/overview/ # OpenNHP:零信任网络隐身协议 {: .fs-9 } +[English](/){: .label .fs-4 } + --- ## 第一章:导读 diff --git a/docs/zh-cn/server_plugin.zh-cn.md b/docs/zh-cn/server_plugin.zh-cn.md index d2e75053..3b4ea0e3 100644 --- a/docs/zh-cn/server_plugin.zh-cn.md +++ b/docs/zh-cn/server_plugin.zh-cn.md @@ -2,19 +2,17 @@ layout: page title: 服务器插件开发 parent: 中文版 -nav_order: 7 +nav_order: 8 permalink: /zh-cn/server_plugin/ --- # OpenNHP插件开发教程 {: .fs-9 } -[English version](/server_plugin/){: .label .fs-4 } +[English](/server_plugin/){: .label .fs-4 } --- -# 目录 - - [简介 ](#简介 ) - [1. 应用OpenNHP插件的必要性 ](#1-应用opennhp插件的必要性 ) diff --git a/release/nhp-ac/shell/restart_door.sh b/release/nhp-ac/shell/restart_ac.sh similarity index 100% rename from release/nhp-ac/shell/restart_door.sh rename to release/nhp-ac/shell/restart_ac.sh diff --git a/release/nhp-ac/shell/run_door.sh b/release/nhp-ac/shell/run_ac.sh similarity index 100% rename from release/nhp-ac/shell/run_door.sh rename to release/nhp-ac/shell/run_ac.sh diff --git a/release/nhp-ac/shell/stop_door.sh b/release/nhp-ac/shell/stop_ac.sh similarity index 100% rename from release/nhp-ac/shell/stop_door.sh rename to release/nhp-ac/shell/stop_ac.sh diff --git a/server/config.go b/server/config.go index a53ed74d..1a55e025 100644 --- a/server/config.go +++ b/server/config.go @@ -1,370 +1,370 @@ -package server - -import ( - "fmt" - "io" - "os" - "path/filepath" - - "github.com/OpenNHP/opennhp/common" - "github.com/OpenNHP/opennhp/core" - "github.com/OpenNHP/opennhp/log" - "github.com/OpenNHP/opennhp/plugins" - "github.com/OpenNHP/opennhp/utils" - - toml "github.com/pelletier/go-toml/v2" -) - -var ( - baseConfigWatch io.Closer - httpConfigWatch io.Closer - acConfigWatch io.Closer - agentConfigWatch io.Closer - resConfigWatch io.Closer - srcipConfigWatch io.Closer - - errLoadConfig = fmt.Errorf("config load error") -) - -type Config struct { - PrivateKeyBase64 string `json:"privateKey"` - ListenIp string `json:"listenIp"` - ListenPort int `json:"listenPort"` - LogLevel int `json:"logLevel"` - Hostname string `json:"hostname"` - DisableAgentValidation bool `json:"disableAgentValidation"` -} - -type HttpConfig struct { - EnableHttp bool - EnableTLS bool - HttpListenIp string - TLSCertFile string - TLSKeyFile string -} - -type Peers struct { - ACs []*core.UdpPeer - Agents []*core.UdpPeer -} - -func (s *UdpServer) loadBaseConfig() error { - // config.toml - fileName := filepath.Join(ExeDirPath, "etc", "config.toml") - if err := s.updateBaseConfig(fileName); err != nil { - // report base config error - return err - } - - baseConfigWatch = utils.WatchFile(fileName, func() { - log.Info("base config: %s has been updated", fileName) - s.updateBaseConfig(fileName) - }) - return nil -} - -func (s *UdpServer) loadHttpConfig() error { - // http.toml - fileName := filepath.Join(ExeDirPath, "etc", "http.toml") - if err := s.updateHttpConfig(fileName); err != nil { - // ignore error - _ = err - } - - httpConfigWatch = utils.WatchFile(fileName, func() { - log.Info("http config: %s has been updated", fileName) - s.updateHttpConfig(fileName) - }) - return nil -} - -func (s *UdpServer) loadPeers() error { - // ac.toml - fileNameAC := filepath.Join(ExeDirPath, "etc", "ac.toml") - if err := s.updateACPeers(fileNameAC); err != nil { - // ignore error - _ = err - } - - acConfigWatch = utils.WatchFile(fileNameAC, func() { - log.Info("ac peer config: %s has been updated", fileNameAC) - s.updateACPeers(fileNameAC) - }) - - // agent.toml - fileNameAgent := filepath.Join(ExeDirPath, "etc", "agent.toml") - if err := s.updateAgentPeers(fileNameAgent); err != nil { - // ignore error - _ = err - } - - agentConfigWatch = utils.WatchFile(fileNameAgent, func() { - log.Info("agent peer config: %s has been updated", fileNameAgent) - s.updateAgentPeers(fileNameAgent) - }) - return nil -} - -func (s *UdpServer) loadResources() error { - // resource.toml - fileName := filepath.Join(ExeDirPath, "etc", "resource.toml") - if err := s.updateResources(fileName); err != nil { - // ignore error - _ = err - } - - resConfigWatch = utils.WatchFile(fileName, func() { - log.Info("resource config: %s has been updated", fileName) - s.updateResources(fileName) - }) - return nil -} - -func (s *UdpServer) loadSourceIps() error { - // srcip.toml - fileName := filepath.Join(ExeDirPath, "etc", "srcip.toml") - if err := s.updateSourceIps(fileName); err != nil { - // ignore error - _ = err - } - - srcipConfigWatch = utils.WatchFile(fileName, func() { - log.Info("src ip config: %s has been updated", fileName) - s.updateSourceIps(fileName) - }) - return nil -} - -func (s *UdpServer) updateBaseConfig(file string) (err error) { - utils.CatchPanicThenRun(func() { - err = errLoadConfig - }) - - content, err := os.ReadFile(file) - if err != nil { - log.Error("failed to read base config: %v", err) - } - - var conf Config - if err := toml.Unmarshal(content, &conf); err != nil { - log.Error("failed to unmarshal base config: %v", err) - } - - if s.config == nil { - s.config = &conf - s.log.SetLogLevel(conf.LogLevel) - return err - } - - // update - if s.config.LogLevel != conf.LogLevel { - log.Info("set base log level to %d", conf.LogLevel) - s.log.SetLogLevel(conf.LogLevel) - s.config.LogLevel = conf.LogLevel - } - - if s.config.DisableAgentValidation != conf.DisableAgentValidation { - if s.device != nil { - s.device.SetOption(core.DeviceOptions{ - DisableAgentPeerValidation: conf.DisableAgentValidation, - }) - } - s.config.DisableAgentValidation = conf.DisableAgentValidation - } - - return err -} - -func (s *UdpServer) updateHttpConfig(file string) (err error) { - utils.CatchPanicThenRun(func() { - err = errLoadConfig - }) - - content, err := os.ReadFile(file) - if err != nil { - log.Error("failed to read base config: %v", err) - } - - var httpConf HttpConfig - if err := toml.Unmarshal(content, &httpConf); err != nil { - log.Error("failed to unmarshal base config: %v", err) - } - - // update - if httpConf.EnableHttp { - // start http server - if s.httpServer == nil || !s.httpServer.IsRunning() { - if s.httpServer != nil { - // stop old http server - go s.httpServer.Stop() - } - hs := &HttpServer{} - s.httpServer = hs - err = hs.Start(s, &httpConf) - if err != nil { - return err - } - } - } else { - // stop http server - if s.httpServer != nil && s.httpServer.IsRunning() { - go s.httpServer.Stop() - s.httpServer = nil - } - } - - s.httpConfig = &httpConf - return err -} - -func (s *UdpServer) updateACPeers(file string) (err error) { - utils.CatchPanicThenRun(func() { - err = errLoadConfig - }) - - content, err := os.ReadFile(file) - if err != nil { - log.Error("failed to read ac peer config: %v", err) - } - - // update - var peers Peers - acPeerMap := make(map[string]*core.UdpPeer) - if err := toml.Unmarshal(content, &peers); err != nil { - log.Error("failed to unmarshal ac peer config: %v", err) - } - for _, p := range peers.ACs { - p.Type = core.NHP_AC - s.device.AddPeer(p) - acPeerMap[p.PublicKeyBase64()] = p - } - - // remove old peers from device - s.acPeerMapMutex.Lock() - defer s.acPeerMapMutex.Unlock() - for pubKey := range s.acPeerMap { - if _, found := acPeerMap[pubKey]; !found { - s.device.RemovePeer(pubKey) - } - } - s.acPeerMap = acPeerMap - - return err -} - -func (s *UdpServer) updateAgentPeers(file string) (err error) { - utils.CatchPanicThenRun(func() { - err = errLoadConfig - }) - - content, err := os.ReadFile(file) - if err != nil { - log.Error("failed to read agent peer config: %v", err) - } - - var peers Peers - agentPeerMap := make(map[string]*core.UdpPeer) - if err := toml.Unmarshal(content, &peers); err != nil { - log.Error("failed to unmarshal agent peer config: %v", err) - } - for _, p := range peers.Agents { - p.Type = core.NHP_AGENT - s.device.AddPeer(p) - agentPeerMap[p.PublicKeyBase64()] = p - } - - // remove old peers from device - s.agentPeerMapMutex.Lock() - defer s.agentPeerMapMutex.Unlock() - for pubKey := range s.agentPeerMap { - if _, found := agentPeerMap[pubKey]; !found { - s.device.RemovePeer(pubKey) - } - } - s.agentPeerMap = agentPeerMap - - return err -} - -func (s *UdpServer) updateResources(file string) (err error) { - utils.CatchPanicThenRun(func() { - err = errLoadConfig - }) - - content, err := os.ReadFile(file) - if err != nil { - log.Error("failed to read resource config: %v", err) - } - - // update - aspMap := make(common.AuthSvcProviderMap) - if err := toml.Unmarshal(content, &aspMap); err != nil { - log.Error("failed to unmarshal resource config: %v", err) - } - - for aspId, aspData := range aspMap { - aspData.AuthSvcId = aspId - if len(aspData.PluginPath) > 0 { - h := plugins.ReadPluginHandler(aspData.PluginPath) - if h != nil { - s.LoadPlugin(aspId, h) - } - } - - for resId, res := range aspData.ResourceGroups { - // Note: res is a pointer, so we can update its value - res.AuthServiceId = aspId - res.ResourceId = resId - } - } - - s.authServiceMapMutex.Lock() - defer s.authServiceMapMutex.Unlock() - s.authServiceMap = aspMap - - return err -} - -func (s *UdpServer) updateSourceIps(file string) (err error) { - utils.CatchPanicThenRun(func() { - err = errLoadConfig - }) - - content, err := os.ReadFile(file) - if err != nil { - log.Error("failed to read src ip config: %v", err) - } - - // update - srcIpMap := make(map[string][]*common.NetAddress) - if err := toml.Unmarshal(content, &srcIpMap); err != nil { - log.Error("failed to unmarshal src ip config: %v", err) - } - - s.srcIpAssociatedAddrMapMutex.Lock() - defer s.srcIpAssociatedAddrMapMutex.Unlock() - s.srcIpAssociatedAddrMap = srcIpMap - - return err -} - -func (s *UdpServer) StopConfigWatch() { - if baseConfigWatch != nil { - baseConfigWatch.Close() - } - if httpConfigWatch != nil { - httpConfigWatch.Close() - } - if acConfigWatch != nil { - acConfigWatch.Close() - } - if agentConfigWatch != nil { - agentConfigWatch.Close() - } - if resConfigWatch != nil { - resConfigWatch.Close() - } - if srcipConfigWatch != nil { - srcipConfigWatch.Close() - } -} +package server + +import ( + "fmt" + "io" + "os" + "path/filepath" + + "github.com/OpenNHP/opennhp/common" + "github.com/OpenNHP/opennhp/core" + "github.com/OpenNHP/opennhp/log" + "github.com/OpenNHP/opennhp/plugins" + "github.com/OpenNHP/opennhp/utils" + + toml "github.com/pelletier/go-toml/v2" +) + +var ( + baseConfigWatch io.Closer + httpConfigWatch io.Closer + acConfigWatch io.Closer + agentConfigWatch io.Closer + resConfigWatch io.Closer + srcipConfigWatch io.Closer + + errLoadConfig = fmt.Errorf("config load error") +) + +type Config struct { + PrivateKeyBase64 string `json:"privateKey"` + ListenIp string `json:"listenIp"` + ListenPort int `json:"listenPort"` + LogLevel int `json:"logLevel"` + Hostname string `json:"hostname"` + DisableAgentValidation bool `json:"disableAgentValidation"` +} + +type HttpConfig struct { + EnableHttp bool + EnableTLS bool + HttpListenIp string + TLSCertFile string + TLSKeyFile string +} + +type Peers struct { + ACs []*core.UdpPeer + Agents []*core.UdpPeer +} + +func (s *UdpServer) loadBaseConfig() error { + // config.toml + fileName := filepath.Join(ExeDirPath, "etc", "config.toml") + if err := s.updateBaseConfig(fileName); err != nil { + // report base config error + return err + } + + baseConfigWatch = utils.WatchFile(fileName, func() { + log.Info("base config: %s has been updated", fileName) + s.updateBaseConfig(fileName) + }) + return nil +} + +func (s *UdpServer) loadHttpConfig() error { + // http.toml + fileName := filepath.Join(ExeDirPath, "etc", "http.toml") + if err := s.updateHttpConfig(fileName); err != nil { + // ignore error + _ = err + } + + httpConfigWatch = utils.WatchFile(fileName, func() { + log.Info("http config: %s has been updated", fileName) + s.updateHttpConfig(fileName) + }) + return nil +} + +func (s *UdpServer) loadPeers() error { + // ac.toml + fileNameAC := filepath.Join(ExeDirPath, "etc", "ac.toml") + if err := s.updateACPeers(fileNameAC); err != nil { + // ignore error + _ = err + } + + acConfigWatch = utils.WatchFile(fileNameAC, func() { + log.Info("ac peer config: %s has been updated", fileNameAC) + s.updateACPeers(fileNameAC) + }) + + // agent.toml + fileNameAgent := filepath.Join(ExeDirPath, "etc", "agent.toml") + if err := s.updateAgentPeers(fileNameAgent); err != nil { + // ignore error + _ = err + } + + agentConfigWatch = utils.WatchFile(fileNameAgent, func() { + log.Info("agent peer config: %s has been updated", fileNameAgent) + s.updateAgentPeers(fileNameAgent) + }) + return nil +} + +func (s *UdpServer) loadResources() error { + // resource.toml + fileName := filepath.Join(ExeDirPath, "etc", "resource.toml") + if err := s.updateResources(fileName); err != nil { + // ignore error + _ = err + } + + resConfigWatch = utils.WatchFile(fileName, func() { + log.Info("resource config: %s has been updated", fileName) + s.updateResources(fileName) + }) + return nil +} + +func (s *UdpServer) loadSourceIps() error { + // srcip.toml + fileName := filepath.Join(ExeDirPath, "etc", "srcip.toml") + if err := s.updateSourceIps(fileName); err != nil { + // ignore error + _ = err + } + + srcipConfigWatch = utils.WatchFile(fileName, func() { + log.Info("src ip config: %s has been updated", fileName) + s.updateSourceIps(fileName) + }) + return nil +} + +func (s *UdpServer) updateBaseConfig(file string) (err error) { + utils.CatchPanicThenRun(func() { + err = errLoadConfig + }) + + content, err := os.ReadFile(file) + if err != nil { + log.Error("failed to read base config: %v", err) + } + + var conf Config + if err := toml.Unmarshal(content, &conf); err != nil { + log.Error("failed to unmarshal base config: %v", err) + } + + if s.config == nil { + s.config = &conf + s.log.SetLogLevel(conf.LogLevel) + return err + } + + // update + if s.config.LogLevel != conf.LogLevel { + log.Info("set base log level to %d", conf.LogLevel) + s.log.SetLogLevel(conf.LogLevel) + s.config.LogLevel = conf.LogLevel + } + + if s.config.DisableAgentValidation != conf.DisableAgentValidation { + if s.device != nil { + s.device.SetOption(core.DeviceOptions{ + DisableAgentPeerValidation: conf.DisableAgentValidation, + }) + } + s.config.DisableAgentValidation = conf.DisableAgentValidation + } + + return err +} + +func (s *UdpServer) updateHttpConfig(file string) (err error) { + utils.CatchPanicThenRun(func() { + err = errLoadConfig + }) + + content, err := os.ReadFile(file) + if err != nil { + log.Error("failed to read http config: %v", err) + } + + var httpConf HttpConfig + if err := toml.Unmarshal(content, &httpConf); err != nil { + log.Error("failed to unmarshal http config: %v", err) + } + + // update + if httpConf.EnableHttp { + // start http server + if s.httpServer == nil || !s.httpServer.IsRunning() { + if s.httpServer != nil { + // stop old http server + go s.httpServer.Stop() + } + hs := &HttpServer{} + s.httpServer = hs + err = hs.Start(s, &httpConf) + if err != nil { + return err + } + } + } else { + // stop http server + if s.httpServer != nil && s.httpServer.IsRunning() { + go s.httpServer.Stop() + s.httpServer = nil + } + } + + s.httpConfig = &httpConf + return err +} + +func (s *UdpServer) updateACPeers(file string) (err error) { + utils.CatchPanicThenRun(func() { + err = errLoadConfig + }) + + content, err := os.ReadFile(file) + if err != nil { + log.Error("failed to read ac peer config: %v", err) + } + + // update + var peers Peers + acPeerMap := make(map[string]*core.UdpPeer) + if err := toml.Unmarshal(content, &peers); err != nil { + log.Error("failed to unmarshal ac peer config: %v", err) + } + for _, p := range peers.ACs { + p.Type = core.NHP_AC + s.device.AddPeer(p) + acPeerMap[p.PublicKeyBase64()] = p + } + + // remove old peers from device + s.acPeerMapMutex.Lock() + defer s.acPeerMapMutex.Unlock() + for pubKey := range s.acPeerMap { + if _, found := acPeerMap[pubKey]; !found { + s.device.RemovePeer(pubKey) + } + } + s.acPeerMap = acPeerMap + + return err +} + +func (s *UdpServer) updateAgentPeers(file string) (err error) { + utils.CatchPanicThenRun(func() { + err = errLoadConfig + }) + + content, err := os.ReadFile(file) + if err != nil { + log.Error("failed to read agent peer config: %v", err) + } + + var peers Peers + agentPeerMap := make(map[string]*core.UdpPeer) + if err := toml.Unmarshal(content, &peers); err != nil { + log.Error("failed to unmarshal agent peer config: %v", err) + } + for _, p := range peers.Agents { + p.Type = core.NHP_AGENT + s.device.AddPeer(p) + agentPeerMap[p.PublicKeyBase64()] = p + } + + // remove old peers from device + s.agentPeerMapMutex.Lock() + defer s.agentPeerMapMutex.Unlock() + for pubKey := range s.agentPeerMap { + if _, found := agentPeerMap[pubKey]; !found { + s.device.RemovePeer(pubKey) + } + } + s.agentPeerMap = agentPeerMap + + return err +} + +func (s *UdpServer) updateResources(file string) (err error) { + utils.CatchPanicThenRun(func() { + err = errLoadConfig + }) + + content, err := os.ReadFile(file) + if err != nil { + log.Error("failed to read resource config: %v", err) + } + + // update + aspMap := make(common.AuthSvcProviderMap) + if err := toml.Unmarshal(content, &aspMap); err != nil { + log.Error("failed to unmarshal resource config: %v", err) + } + + for aspId, aspData := range aspMap { + aspData.AuthSvcId = aspId + if len(aspData.PluginPath) > 0 { + h := plugins.ReadPluginHandler(aspData.PluginPath) + if h != nil { + s.LoadPlugin(aspId, h) + } + } + + for resId, res := range aspData.ResourceGroups { + // Note: res is a pointer, so we can update its value + res.AuthServiceId = aspId + res.ResourceId = resId + } + } + + s.authServiceMapMutex.Lock() + defer s.authServiceMapMutex.Unlock() + s.authServiceMap = aspMap + + return err +} + +func (s *UdpServer) updateSourceIps(file string) (err error) { + utils.CatchPanicThenRun(func() { + err = errLoadConfig + }) + + content, err := os.ReadFile(file) + if err != nil { + log.Error("failed to read src ip config: %v", err) + } + + // update + srcIpMap := make(map[string][]*common.NetAddress) + if err := toml.Unmarshal(content, &srcIpMap); err != nil { + log.Error("failed to unmarshal src ip config: %v", err) + } + + s.srcIpAssociatedAddrMapMutex.Lock() + defer s.srcIpAssociatedAddrMapMutex.Unlock() + s.srcIpAssociatedAddrMap = srcIpMap + + return err +} + +func (s *UdpServer) StopConfigWatch() { + if baseConfigWatch != nil { + baseConfigWatch.Close() + } + if httpConfigWatch != nil { + httpConfigWatch.Close() + } + if acConfigWatch != nil { + acConfigWatch.Close() + } + if agentConfigWatch != nil { + agentConfigWatch.Close() + } + if resConfigWatch != nil { + resConfigWatch.Close() + } + if srcipConfigWatch != nil { + srcipConfigWatch.Close() + } +} diff --git a/server/constants.go b/server/constants.go index f1eb6a5d..0b0b1e37 100644 --- a/server/constants.go +++ b/server/constants.go @@ -18,6 +18,7 @@ const ( // knock const ( - DefaultIpOpenTime = 120 // second, align with ipset default timeout - ACOpenCompensationTime = 5 // second + DefaultIpOpenTime = 120 // second, align with ipset default timeout + ACOpenCompensationTime = 5 // second + TokenStoreRefreshInterval = 10 // second ) diff --git a/server/httpserver.go b/server/httpserver.go index 3ca15ec2..8b6e4d50 100644 --- a/server/httpserver.go +++ b/server/httpserver.go @@ -253,6 +253,30 @@ func (hs *HttpServer) initRouter() { } hs.legacyAuthWithAspPlugin(ctx, req) }) + + /* + refreshGrp := g.Group("refresh") + refreshGrp.GET("/:token", func(ctx *gin.Context) { + var err error + token := ctx.Param("token") + log.Info("get refresh request. aspId: %s, query: %v", token, ctx.Request.URL.RawQuery) + + if len(token) == 0 { + err = common.ErrUrlPathInvalid + log.Error("path error: %v", err) + ctx.String(http.StatusOK, "{\"errMsg\": \"path error: %v\"}", err) + return + } + + req := &common.HttpRefreshRequest{ + Token: token, + SrcIp: ctx.Query("srcip"), + } + + hs.handleRefreshResource() + }) + */ + } // corsMiddleware is a middleware function that adds CORS headers to the HTTP response. @@ -263,8 +287,8 @@ func corsMiddleware() gin.HandlerFunc { // HTTP headers for CORS c.Writer.Header().Set("Access-Control-Allow-Origin", "*") // allow cross-origin resource sharing c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS, POST") // methods - c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Type, Content-Length") - c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Authorization, X-NHP-Ver") + c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Type, Content-Length, Set-Cookie") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Authorization, X-NHP-Ver, Cookie") c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") c.Writer.Header().Set("Access-Control-Max-Age", "300") // NHP headers @@ -316,13 +340,13 @@ func (hs *HttpServer) handleHttpOpenResource(req *common.HttpKnockRequest, res * srcAddr := &common.NetAddress{Ip: srcIp} acDstIpMap := make(map[string][]*common.NetAddress) - for _, info := range res.Resources { - addrs, exist := acDstIpMap[info.ACId] + for resName, info := range res.Resources { + addrs, exist := acDstIpMap[resName] if exist { addrs = append(addrs, info.Addr) - acDstIpMap[info.ACId] = addrs + acDstIpMap[resName] = addrs } else { - acDstIpMap[info.ACId] = []*common.NetAddress{info.Addr} + acDstIpMap[resName] = []*common.NetAddress{info.Addr} } } @@ -330,8 +354,11 @@ func (hs *HttpServer) handleHttpOpenResource(req *common.HttpKnockRequest, res * var acWg sync.WaitGroup var artMsgsMutex sync.Mutex artMsgs := make(map[string]*common.ACOpsResultMsg) + ackMsg.ACTokens = make(map[string]string) + ackMsg.PreAccessActions = make(map[string]*common.PreAccessInfo) - for acId, addrs := range acDstIpMap { + for resName, addrs := range acDstIpMap { + acId := res.Resources[resName].ACId s.acConnectionMapMutex.Lock() acConn, found := s.acConnectionMap[acId] s.acConnectionMapMutex.Unlock() @@ -344,14 +371,16 @@ func (hs *HttpServer) handleHttpOpenResource(req *common.HttpKnockRequest, res * } acWg.Add(1) - go func(acip string, dstAddrs []*common.NetAddress) { + go func(name string, dstAddrs []*common.NetAddress) { defer acWg.Done() artMsg, _ := s.processACOperation(knkMsg, acConn, srcAddr, dstAddrs, res.OpenTime) artMsgsMutex.Lock() - artMsgs[acip] = artMsg + artMsgs[name] = artMsg + ackMsg.ACTokens[name] = artMsg.ACToken + ackMsg.PreAccessActions[name] = artMsg.PreAccessAction artMsgsMutex.Unlock() - }(acId, addrs) + }(resName, addrs) } acWg.Wait() @@ -359,7 +388,7 @@ func (hs *HttpServer) handleHttpOpenResource(req *common.HttpKnockRequest, res * for _, artMsg := range artMsgs { if artMsg.ErrCode != common.ErrSuccess.ErrorCode() { errCount++ - break + continue } } @@ -393,3 +422,8 @@ func (hs *HttpServer) NewHttpServerHelper() *plugins.HttpServerPluginHelper { func (hs *HttpServer) FindPluginHandler(aspId string) plugins.PluginHandler { return hs.udpServer.FindPluginHandler(aspId) } + +func (hs *HttpServer) handleRefreshResource(token string) (err error) { + // to do + return nil +} diff --git a/server/main/etc/ac.toml b/server/main/etc/ac.toml index ccbc2c3c..a4642d59 100644 --- a/server/main/etc/ac.toml +++ b/server/main/etc/ac.toml @@ -4,4 +4,4 @@ # ExpireTime (epoch timestamp in seconds): peer key validation will fail when it expires. [[ACs]] PubKeyBase64 = "Fr5jzZDVpNh5m9AcBDMtHGmbCAczHyPegT8IxQ3XAzE=" -ExpireTime = 1716345064 +ExpireTime = 1924991999 diff --git a/server/main/etc/agent.toml b/server/main/etc/agent.toml index 7a1252d3..c5cd0c31 100644 --- a/server/main/etc/agent.toml +++ b/server/main/etc/agent.toml @@ -4,4 +4,4 @@ # ExpireTime (epoch timestamp in seconds): peer key validation will fail when it expires. [[Agents]] PubKeyBase64 = "WnJAolo88/q0x2VdLQYdmZNtKjwG2ocBd1Ozj41AKlo=" -ExpireTime = 1716345064 +ExpireTime = 1924991999 diff --git a/server/plugins/example/etc/resource.toml b/server/plugins/example/etc/resource.toml index 7b779a22..9defa85b 100644 --- a/server/plugins/example/etc/resource.toml +++ b/server/plugins/example/etc/resource.toml @@ -9,9 +9,9 @@ ["demo"] SkipAuth = true OpenTime = 120 -RedirectUrl = "https://acdemo.opennhp.cn" +RedirectUrl = "https://acdemo.opennhp.org" RedirectWithParams = false -CookieDomain = "opennhp.cn" +CookieDomain = "opennhp.org" # syntax ["{ResourceId}".Resources."{ResourceName}"] # ResourceName: name of resource inside a resource group. Each ResourceId can have multiple ResourceNames. @@ -23,7 +23,7 @@ CookieDomain = "opennhp.cn" # PortSuffix: true: fill ack field "resHost" with "{Hostname}:{Port}", false: fill ack field "resHost" with just "{Hostname}" ["demo".Resources."demoServer"] ACId = "testAC-1" -Hostname = "acdemo.opennhp.cn" +Hostname = "acdemo.opennhp.org" Addr.Ip = "220.191.216.236" Addr.Port = 443 # empty or 0 for all ports Addr.Protocol = "" # "tcp/udp/any", empty for "any" diff --git a/server/plugins/example/main.go b/server/plugins/example/main.go index e6431ffc..19dcbe16 100644 --- a/server/plugins/example/main.go +++ b/server/plugins/example/main.go @@ -244,14 +244,33 @@ func authRegular(ctx *gin.Context, req *common.HttpKnockRequest, res *common.Res log.Error("RedirectUrl is not provided.") } else { ackMsg.RedirectUrl = res.RedirectUrl - ctx.SetCookie( - "nhp-token", // Name - "example-nhp-token-GUBdoVXpxt", // Value - -1, // MaxAge - "/", // Path - res.CookieDomain, // Domain - true, // Secure - true) // HttpOnly + } + + // set cookies + // note that a dot in domain prefix used to make a difference, but now it doesn't (RFC6265). + // The cookie will be sent to any subdomain of the specified domain, with or without the leading dot. + singleHost := len(ackMsg.ACTokens) == 1 + for resName, token := range ackMsg.ACTokens { + if singleHost { + ctx.SetCookie( + "nhp-token", // Name + url.QueryEscape(token), // Value + int(res.OpenTime), // MaxAge - use the knock interval time + "/", // Path + res.CookieDomain, // Domain + true, // Secure - if true, this cookie will only be sent on https, not http + true) // HttpOnly - if true, this cookie will only be sent on http(s) + } else { + domain := strings.Split(ackMsg.ResourceHost[resName], ":")[0] + ctx.SetCookie( + "nhp-token"+"/"+resName, // Name + url.QueryEscape(token), // Value + int(res.OpenTime), // MaxAge - use the knock interval time + "/", // Path + domain, // Domain + true, // Secure - if true, this cookie will only be sent on https, not http + true) // HttpOnly - if true, this cookie will only be sent on http(s) + } log.Info("ctx.SetCookie.") } } diff --git a/server/plugins/example/templates/example_acdemo.html b/server/plugins/example/templates/example_acdemo.html index 5569f82e..c306c602 100644 --- a/server/plugins/example/templates/example_acdemo.html +++ b/server/plugins/example/templates/example_acdemo.html @@ -1,4 +1,4 @@ - + @@ -32,6 +32,12 @@ margin-top: 1rem; font-weight: bold; } + #countdown { + margin-top: 2rem; + color: red; + font-size: 1.5rem; + font-weight: bold; + } #message { margin-top: 1rem; color: green; @@ -45,7 +51,35 @@

Welcome to the Demo NHP-AC Server

acdemo.opennhp.org

-
+ +
This Server Will be Hidden after 15 seconds ...
+ +
The Server Has been Hidden.
+ + + diff --git a/server/tokenstore.go b/server/tokenstore.go new file mode 100644 index 00000000..53a64ef9 --- /dev/null +++ b/server/tokenstore.go @@ -0,0 +1,99 @@ +package server + +import ( + "encoding/base64" + "encoding/binary" + "time" + + "github.com/emmansun/gmsm/sm3" + + "github.com/OpenNHP/opennhp/common" + "github.com/OpenNHP/opennhp/log" +) + +type ACTokenEntry struct { + User *common.AgentUser + ResourceId string + ACTokens map[string]string + OpenTime int + ExpireTime time.Time +} + +type TokenToACMap = map[string]*ACTokenEntry // server access token mapped into mutiple AC tokens +type TokenStore = map[string]TokenToACMap // upper layer of tokens, indexed by first two characters + +func (s *UdpServer) GenerateAccessToken(entry *ACTokenEntry) string { + var tsBytes [8]byte + currTime := time.Now().UnixNano() + + hash := sm3.New() + binary.BigEndian.PutUint64(tsBytes[:], uint64(currTime)) + au := entry.User + hash.Write([]byte(s.config.Hostname + au.UserId + au.DeviceId + au.OrganizationId + au.AuthServiceId)) + hash.Write(tsBytes[:]) + token := base64.StdEncoding.EncodeToString(hash.Sum(nil)) + hash.Reset() + + s.tokenStoreMutex.Lock() + defer s.tokenStoreMutex.Unlock() + + entry.ExpireTime = time.Now().Add(time.Duration(entry.OpenTime) * time.Second) + tokenMap, found := s.tokenStore[token[0:1]] + if found { + tokenMap[token] = entry + } else { + tokenMap := make(TokenToACMap) + tokenMap[token] = entry + s.tokenStore[token[0:1]] = tokenMap + } + + return token +} + +func (s *UdpServer) VerifyAccessToken(token string) *ACTokenEntry { + s.tokenStoreMutex.Lock() + defer s.tokenStoreMutex.Unlock() + + tokenMap, found := s.tokenStore[token[0:1]] + if found { + entry, found := tokenMap[token] + if found { + return entry + } + } + + return nil +} + +func (s *UdpServer) tokenStoreRefreshRoutine() { + defer s.wg.Done() + defer log.Info("tokenStoreRefreshRoutine stopped") + + log.Info("tokenStoreRefreshRoutine started") + + for { + select { + case <-s.signals.stop: + return + + case <-time.After(TokenStoreRefreshInterval * time.Second): + func() { + s.tokenStoreMutex.Lock() + defer s.tokenStoreMutex.Unlock() + + now := time.Now() + for head, tokenMap := range s.tokenStore { + for token, entry := range tokenMap { + if now.After(entry.ExpireTime) { + log.Info("[TokenStore] token %s expired, remove", token) + delete(tokenMap, token) + } + } + if len(tokenMap) == 0 { + delete(s.tokenStore, head) + } + } + }() + } + } +} diff --git a/server/udpserver.go b/server/udpserver.go index e322a352..ccb5e1f3 100644 --- a/server/udpserver.go +++ b/server/udpserver.go @@ -55,6 +55,9 @@ type UdpServer struct { acPeerMapMutex sync.Mutex acPeerMap map[string]*core.UdpPeer // indexed by peer's public key base64 string + tokenStoreMutex sync.Mutex + tokenStore TokenStore + // block address management blockAddrMapMutex sync.Mutex blockAddrMap map[string]*BlockAddr // indexed by remote UDP address, need lock for dynamic change @@ -189,6 +192,7 @@ func (s *UdpServer) Start(dirPath string, logLevel int) (err error) { s.remoteConnectionMap = make(map[string]*UdpConn) s.acConnectionMap = make(map[string]*ACConn) + s.tokenStore = make(TokenStore) s.blockAddrMap = make(map[string]*BlockAddr) s.signals.stop = make(chan struct{}) @@ -199,7 +203,8 @@ func (s *UdpServer) Start(dirPath string, logLevel int) (err error) { s.device.Start() // start server routines - s.wg.Add(4) + s.wg.Add(5) + go s.tokenStoreRefreshRoutine() go s.BlockAddrRefreshRoutine() go s.recvPacketRoutine() go s.sendMessageRoutine() @@ -855,13 +860,13 @@ func (s *UdpServer) handleNhpOpenResource(req *common.NhpAuthRequest, res *commo ackMsg = req.Ack acDstIpMap := make(map[string][]*common.NetAddress) - for _, info := range res.Resources { - addrs, exist := acDstIpMap[info.ACId] + for resName, info := range res.Resources { + addrs, exist := acDstIpMap[resName] if exist { addrs = append(addrs, info.Addr) - acDstIpMap[info.ACId] = addrs + acDstIpMap[resName] = addrs } else { - acDstIpMap[info.ACId] = []*common.NetAddress{info.Addr} + acDstIpMap[resName] = []*common.NetAddress{info.Addr} } } @@ -869,8 +874,11 @@ func (s *UdpServer) handleNhpOpenResource(req *common.NhpAuthRequest, res *commo var acWg sync.WaitGroup var artMsgsMutex sync.Mutex artMsgs := make(map[string]*common.ACOpsResultMsg) + ackMsg.ACTokens = make(map[string]string) + ackMsg.PreAccessActions = make(map[string]*common.PreAccessInfo) - for acId, dstAddrs := range acDstIpMap { + for resName, dstAddrs := range acDstIpMap { + acId := res.Resources[resName].ACId s.acConnectionMapMutex.Lock() acConn, found := s.acConnectionMap[acId] s.acConnectionMapMutex.Unlock() @@ -883,7 +891,7 @@ func (s *UdpServer) handleNhpOpenResource(req *common.NhpAuthRequest, res *commo } acWg.Add(1) - go func(id string, addrs []*common.NetAddress) { + go func(name string, addrs []*common.NetAddress) { defer acWg.Done() openTime := res.OpenTime @@ -892,9 +900,11 @@ func (s *UdpServer) handleNhpOpenResource(req *common.NhpAuthRequest, res *commo } artMsg, _ := s.processACOperation(knkMsg, acConn, srcAddr, addrs, openTime) artMsgsMutex.Lock() - artMsgs[id] = artMsg + artMsgs[name] = artMsg + ackMsg.ACTokens[name] = artMsg.ACToken + ackMsg.PreAccessActions[name] = artMsg.PreAccessAction artMsgsMutex.Unlock() - }(acId, dstAddrs) + }(resName, dstAddrs) } acWg.Wait() @@ -914,13 +924,6 @@ func (s *UdpServer) handleNhpOpenResource(req *common.NhpAuthRequest, res *commo return } - ackMsg.PreAccessActions = make([]*common.PreAccessInfo, 0, len(artMsgs)) - for _, artMsg := range artMsgs { - if artMsg.PreAccessAction != nil { - ackMsg.PreAccessActions = append(ackMsg.PreAccessActions, artMsg.PreAccessAction) - } - } - ackMsg.ErrCode = common.ErrSuccess.ErrorCode() ackMsg.ErrMsg = common.ErrSuccess.Error() return ackMsg, nil