From f162e2abb6748cd592f66aa4f3679c7ecddea797 Mon Sep 17 00:00:00 2001 From: Ivan Zorin Date: Mon, 8 Jul 2024 00:23:23 +0300 Subject: [PATCH] Initial commit --- .gitattributes | 2 + .github/workflows/build-and-test.yml | 53 ++ .gitignore | 15 + CODE_OF_CONDUCT.md | 128 ++++ CODE_OF_CONDUCT_ru.md | 79 +++ CONTRIBUTING.md | 34 + CONTRIBUTING_ru.md | 33 + Cargo.toml | 76 +++ LICENSE | 661 ++++++++++++++++++ README.md | 55 ++ rustfmt.toml | 18 + src/kind.rs | 860 ++++++++++++++++++++++++ src/lib.rs | 136 ++++ src/v1/aeadcipher/aes_ccm.rs | 86 +++ src/v1/aeadcipher/aes_gcm.rs | 147 ++++ src/v1/aeadcipher/aes_gcm_siv.rs | 88 +++ src/v1/aeadcipher/chacha20_poly1305.rs | 97 +++ src/v1/aeadcipher/mod.rs | 223 ++++++ src/v1/aeadcipher/sm4_ccm.rs | 83 +++ src/v1/aeadcipher/sm4_gcm.rs | 46 ++ src/v1/aeadcipher/sm4_gcm_cipher.rs | 202 ++++++ src/v1/aeadcipher/xchacha20_poly1305.rs | 47 ++ src/v1/cipher.rs | 231 +++++++ src/v1/dummy.rs | 41 ++ src/v1/mod.rs | 11 + src/v1/streamcipher/cfb.rs | 426 ++++++++++++ src/v1/streamcipher/chacha20.rs | 39 ++ src/v1/streamcipher/crypto/aes.rs | 75 +++ src/v1/streamcipher/crypto/camellia.rs | 74 ++ src/v1/streamcipher/crypto/mod.rs | 3 + src/v1/streamcipher/crypto/rc4.rs | 119 ++++ src/v1/streamcipher/ctr.rs | 248 +++++++ src/v1/streamcipher/mod.rs | 222 ++++++ src/v1/streamcipher/ofb.rs | 174 +++++ src/v1/streamcipher/rc4.rs | 37 + src/v1/streamcipher/rc4_md5.rs | 66 ++ src/v1/streamcipher/table.rs | 126 ++++ src/v2/crypto/aes_gcm.rs | 1 + src/v2/crypto/chacha20_poly1305.rs | 1 + src/v2/crypto/chacha8_poly1305.rs | 47 ++ src/v2/crypto/mod.rs | 18 + src/v2/crypto/xchacha20_poly1305.rs | 1 + src/v2/crypto/xchacha8_poly1305.rs | 47 ++ src/v2/mod.rs | 8 + src/v2/tcp/mod.rs | 155 +++++ src/v2/udp/aes_gcm.rs | 56 ++ src/v2/udp/chacha20_poly1305.rs | 28 + src/v2/udp/chacha8_poly1305.rs | 28 + src/v2/udp/mod.rs | 93 +++ 49 files changed, 5544 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/workflows/build-and-test.yml create mode 100644 .gitignore create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CODE_OF_CONDUCT_ru.md create mode 100644 CONTRIBUTING.md create mode 100644 CONTRIBUTING_ru.md create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 rustfmt.toml create mode 100644 src/kind.rs create mode 100644 src/lib.rs create mode 100644 src/v1/aeadcipher/aes_ccm.rs create mode 100644 src/v1/aeadcipher/aes_gcm.rs create mode 100644 src/v1/aeadcipher/aes_gcm_siv.rs create mode 100644 src/v1/aeadcipher/chacha20_poly1305.rs create mode 100644 src/v1/aeadcipher/mod.rs create mode 100644 src/v1/aeadcipher/sm4_ccm.rs create mode 100644 src/v1/aeadcipher/sm4_gcm.rs create mode 100644 src/v1/aeadcipher/sm4_gcm_cipher.rs create mode 100644 src/v1/aeadcipher/xchacha20_poly1305.rs create mode 100644 src/v1/cipher.rs create mode 100644 src/v1/dummy.rs create mode 100644 src/v1/mod.rs create mode 100644 src/v1/streamcipher/cfb.rs create mode 100644 src/v1/streamcipher/chacha20.rs create mode 100644 src/v1/streamcipher/crypto/aes.rs create mode 100644 src/v1/streamcipher/crypto/camellia.rs create mode 100644 src/v1/streamcipher/crypto/mod.rs create mode 100644 src/v1/streamcipher/crypto/rc4.rs create mode 100644 src/v1/streamcipher/ctr.rs create mode 100644 src/v1/streamcipher/mod.rs create mode 100644 src/v1/streamcipher/ofb.rs create mode 100644 src/v1/streamcipher/rc4.rs create mode 100644 src/v1/streamcipher/rc4_md5.rs create mode 100644 src/v1/streamcipher/table.rs create mode 100644 src/v2/crypto/aes_gcm.rs create mode 100644 src/v2/crypto/chacha20_poly1305.rs create mode 100644 src/v2/crypto/chacha8_poly1305.rs create mode 100644 src/v2/crypto/mod.rs create mode 100644 src/v2/crypto/xchacha20_poly1305.rs create mode 100644 src/v2/crypto/xchacha8_poly1305.rs create mode 100644 src/v2/mod.rs create mode 100644 src/v2/tcp/mod.rs create mode 100644 src/v2/udp/aes_gcm.rs create mode 100644 src/v2/udp/chacha20_poly1305.rs create mode 100644 src/v2/udp/chacha8_poly1305.rs create mode 100644 src/v2/udp/mod.rs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..2c7f8e0 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,53 @@ +name: Build & Test + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + CARGO_TERM_COLOR: always + +jobs: + buid-test-check: + strategy: + matrix: + platform: + - ubuntu-latest + - windows-latest + - macos-latest + runs-on: ${{ matrix.platform }} + + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Install Rust nightly + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + profile: minimal + components: clippy + override: true + - name: Build & Test (Default) + run: cargo test --verbose --no-fail-fast + - name: Build & Test (--no-default-features) + run: cargo test --verbose --no-default-features --no-fail-fast + - name: Build & Test with All Features Enabled + run: cargo test --verbose --all-features + - name: Clippy Check + uses: actions-rs/clippy-check@v1 + with: + name: clippy-${{ matrix.platform }} + token: ${{ secrets.GITHUB_TOKEN }} + args: | + --verbose --all-features -- -Z macro-backtrace + -W clippy::absurd_extreme_comparisons + -W clippy::erasing_op + -A clippy::collapsible_else_if diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f66e149 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# Editors +.bak +.vscode +.swp diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..c45f453 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct +[Документ на русском языке](CODE_OF_CONDUCT_ru.md) +## 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 +support@localzet.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/CODE_OF_CONDUCT_ru.md b/CODE_OF_CONDUCT_ru.md new file mode 100644 index 0000000..c00761f --- /dev/null +++ b/CODE_OF_CONDUCT_ru.md @@ -0,0 +1,79 @@ +# Кодекс поведения участника + +## Наше обязательство + +Мы, как участники, авторы и лидеры, обязуемся сделать участие в нашем сообществе свободным от преследований для всех, независимо от возраста, телосложения, видимых или невидимых инвалидностей, этнической принадлежности, половых признаков, гендерной идентичности и выражения, уровня опыта, образования, социально-экономического статуса, национальности, внешности, расы, религии или сексуальной идентичности и ориентации. + +Мы обязуемся действовать и взаимодействовать таким образом, чтобы способствовать открытому, гостеприимному, разнообразному, инклюзивному и здоровому сообществу. + +## Наши стандарты + +Примеры поведения, которые способствуют созданию положительной среды в нашем сообществе, включают: + +* Проявление эмпатии и доброты к другим людям +* Уважение к различным мнениям, взглядам и опыту +* Добросовестное принятие и предоставление конструктивной обратной связи +* Принятие ответственности, извинения перед теми, кого затронули наши ошибки, и извлечение уроков из опыта +* Сосредоточение на том, что лучше не только для нас, как индивидов, но и для всего сообщества + +Примеры недопустимого поведения включают: + +* Использование сексуализированного языка или образов, а также нежелательное сексуальное внимание или домогательства +* Троллинг, оскорбительные или уничижительные комментарии, а также личные или политические атаки +* Публичное или частное преследование +* Публикация частной информации других людей, например, физического или электронного адреса, без явного разрешения +* Другое поведение, которое можно было бы обоснованно считать неприемлемым в профессиональной среде + +## Обязанности по обеспечению соблюдения + +Лидеры сообщества отвечают за разъяснение и применение наших стандартов приемлемого поведения и будут предпринимать соответствующие и справедливые корректирующие меры в ответ на любое поведение, которое они считают неприемлемым, угрожающим, оскорбительным или вредным. + +Лидеры сообщества имеют право и обязанность удалять, редактировать или отклонять комментарии, коммиты, код, правки вики, вопросы и другие вклады, которые не соответствуют этому Кодексу поведения, и будут сообщать причины решений о модерации, когда это уместно. + +## Область применения + +Этот Кодекс поведения действует во всех пространствах сообщества и также применяется, когда индивидуум официально представляет сообщество в общественных пространствах. Примеры представления нашего сообщества включают использование официального адреса электронной почты, публикацию через официальный аккаунт в социальных сетях или действие в качестве назначенного представителя на онлайн- или офлайн-мероприятии. + +## Применение + +Случаи оскорбительного, преследующего или иным образом неприемлемого поведения могут быть сообщены ответственным за обеспечение соблюдения в сообществе по адресу support@localzet.com. Все жалобы будут рассмотрены и расследованы оперативно и справедливо. + +Все лидеры сообщества обязаны уважать конфиденциальность и безопасность докладчика любого инцидента. + +## Руководство по применению + +Лидеры сообщества будут следовать этим Руководствам по воздействию на сообщество при определении последствий за любые действия, которые они считают нарушением этого Кодекса поведения: + +### 1. Исправление + +**Воздействие на сообщество**: Использование неприемлемого языка или другого поведения, считающегося непрофессиональным или нежелательным в сообществе. + +**Последствия**: Частное, письменное предупреждение от лидеров сообщества, обеспечивающее ясность в отношении характера нарушения и объяснение того, почему поведение было неприемлемым. Может быть запрошено публичное извинение. + +### 2. Предупреждение + +**Воздействие на сообщество**: Нарушение через один инцидент или серию действий. + +**Последствия**: Предупреждение с последствиями за продолжение поведения. Никакого взаимодействия с задействованными людьми, включая нежелательное взаимодействие с теми, кто обеспечивает соблюдение Кодекса поведения, в течение определенного периода времени. Это включает в себя избегание взаимодействий в пространствах сообщества, а также внешних каналов, таких как социальные сети. Нарушение этих условий может привести к временному или постоянному бану. + +### 3. Временный бан + +**Воздействие на сообщество**: Серьезное нарушение стандартов сообщества, включая продолжительное неприемлемое поведение. + +**Последствия**: Временный бан от любого вида взаимодействия или общения в пространствах сообщества на определенный период времени. Никакого взаимодействия с задействованными людьми, включая нежелательное взаимодействие с теми, кто обеспечивает соблюдение Кодекса поведения, в течение этого периода времени. + +### 4. Постоянный бан + +**Воздействие на сообщество**: Демонстрация модели нарушения стандартов сообщества, включая продолжительное неприемлемое поведение, преследование индивидуума или агрессия или презрение к классам индивидуумов. + +**Последствия**: Постоянный бан от любого вида публичного взаимодействия в сообществе. + +## Атрибуция + +Этот Кодекс поведения адаптирован из [Contributor Covenant][homepage], +версия 2.0, доступная по адресу +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Руководство по воздействию на сообщество было вдохновлено лестницей обеспечения соблюдения кодекса поведения Mozilla. + +[homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3c7f67f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# Contributing to the Project +[Документ на русском языке](CONTRIBUTING_ru.md) + +We welcome you to our project and thank you for your interest in our code! + +## Getting Started + +Before you begin, please familiarize yourself with our rules and guidelines: + +- Code of Conduct +- Code Style Guide + +## How to Contribute + +There are many ways you can contribute to the project: + +- Suggest improvements or new features +- Submit bug fixes +- Improve documentation + +## Creating a Pull Request + +1. Fork the repository. +2. Create a new branch. +3. Make your changes. +4. Submit a pull request. + +Please ensure that your code adheres to our code style guide and all tests pass. + +## Contact + +If you have any questions, feel free to reach out to us through the GitHub issues system. + +Thank you for your contribution! diff --git a/CONTRIBUTING_ru.md b/CONTRIBUTING_ru.md new file mode 100644 index 0000000..f504204 --- /dev/null +++ b/CONTRIBUTING_ru.md @@ -0,0 +1,33 @@ +# Вклад в проект + +Мы рады приветствовать вас в нашем проекте и благодарим за ваш интерес к нашему коду! + +## Как начать + +Перед тем как начать, пожалуйста, ознакомьтесь с нашими правилами и руководствами: + +- Кодекс поведения +- Руководство по стилю кода + +## Как внести свой вклад + +Есть много способов внести свой вклад в проект: + +- Предложить улучшения или новые функции +- Отправить исправления ошибок +- Улучшить документацию + +## Создание запроса на слияние (Pull Request) + +1. Сделайте форк репозитория. +2. Создайте новую ветку. +3. Внесите свои изменения. +4. Отправьте запрос на слияние. + +Пожалуйста, убедитесь, что ваш код соответствует нашему руководству по стилю кода и все тесты проходят. + +## Связь + +Если у вас есть вопросы, не стесняйтесь обращаться к нам через систему вопросов GitHub. + +Спасибо за ваш вклад! diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4873164 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,76 @@ +[package] +name = "shadowsocks-crypto" +version = "0.5.5" +authors = ["luozijun ", "ty "] +edition = "2021" +license = "MIT" +keywords = ["Cryptography"] +description = "Shadowsocks Crypto" +repository = "https://github.com/shadowsocks/shadowsocks-crypto" +documentation = "https://docs.rs/shadowsocks-crypto" +rust-version = "1.61" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["v1", "v1-aead"] +v1 = [] +v1-stream = ["v1", "chacha20", "aes", "ctr", "camellia"] +v1-aead = ["v1", "aes-gcm", "chacha20poly1305", "hkdf", "sha1"] +v1-aead-extra = [ + "v1-aead", + "aes-gcm-siv", + "ccm", + "aes", + "sm4", + "ghash", + "aead", + "subtle", + "ctr", +] +v2 = ["aes", "aes-gcm", "blake3", "chacha20poly1305", "bytes"] +v2-extra = ["v2", "chacha20poly1305/reduced-round"] + +ring = ["ring-compat"] + +[dependencies] +cfg-if = "1.0" +rand = "0.8" +aes-gcm = { version = "0.10", optional = true } +aes-gcm-siv = { version = "0.11", optional = true } +ccm = { version = "0.5", optional = true } +chacha20poly1305 = { version = "0.10", optional = true } +ring-compat = { version = "0.8", optional = true } +md-5 = { version = "0.10" } +hkdf = { version = "0.12", optional = true } +sha1 = { version = "0.10", optional = true } +blake3 = { version = "1.3", optional = true } +chacha20 = { version = "0.9", features = [], optional = true } +aes = { version = "0.8", optional = true } +ctr = { version = "0.9", optional = true } +bytes = { version = "1.3", optional = true } +camellia = { version = "0.1", optional = true } +sm4 = { version = "0.5", optional = true } +ghash = { version = "0.5", optional = true } +aead = { version = "0.5", optional = true } +subtle = { version = "2.5", optional = true } + +#[target.'cfg(all(unix, any(target_arch = "x86", target_arch = "x86_64")))'.dependencies] +#md-5 = { version = "0.10", features = ["asm"] } +#sha1 = { version = "0.10", features = ["asm"], optional = true } + +#[target.'cfg(all(unix, target_arch = "aarch64", any(target_os = "linux", target_os = "macos")))'.dependencies] +#sha1 = { version = "0.10", features = ["asm"], optional = true } + +#[target.'cfg(all(windows, any(target_arch = "x86", target_arch = "x86_64"), target_env = "gnu"))'.dependencies] +#md-5 = { version = "0.10", features = ["asm"] } +#sha1 = { version = "0.10", features = ["asm"], optional = true } + +#[target.'cfg(all(windows, target_arch = "aarch64", target_env = "gnu"))'.dependencies] +#sha1 = { version = "0.10", features = ["asm"], optional = true } + +[dev-dependencies] +hex = "0.4" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e20b431 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ +GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e1070d7 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +

+ +

+ +

+ + Коммиты + + + Релизы + + + Язык + + + Лицензия + +

+ +# shadowsocks-crypto + +[![Build & Test](https://github.com/localzet/shadowsocks-crypto/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/localzet/shadowsocks-crypto/actions/workflows/build-and-test.yml) + +shadowsocks' flavored cryptographic algorithm in pure Rust. + +## Supported Ciphers + +Stream Ciphers: + +* [x] SS\_TABLE +* [x] SS\_RC4\_MD5 +* [x] AES\_128\_CTR, AES\_192\_CTR, AES\_256\_CTR +* [x] AES\_128\_CFB1, AES\_128\_CFB8, AES\_128\_CFB128, AES\_192\_CFB1, AES\_192\_CFB8, AES\_192\_CFB128, AES\_256\_CFB1, AES\_256\_CFB8, AES\_256\_CFB128 +* [x] AES\_128\_OFB, AES\_192\_OFB, AES\_256\_OFB +* [x] CAMELLIA\_128\_CTR, CAMELLIA\_192\_CTR, CAMELLIA\_256\_CTR +* [x] CAMELLIA\_128\_CFB1, CAMELLIA\_128\_CFB8, CAMELLIA\_128\_CFB128, CAMELLIA\_192\_CFB1, CAMELLIA\_192\_CFB8, CAMELLIA\_192\_CFB128, CAMELLIA\_256\_CFB1, CAMELLIA\_256\_CFB8, CAMELLIA\_256\_CFB128 +* [x] CAMELLIA\_128\_OFB, CAMELLIA\_192\_OFB, CAMELLIA\_256\_OFB +* [x] RC4 +* [x] CHACHA20 (IETF Version) + +AEAD Ciphers: + +* [x] AES\_128\_CCM, AES\_256\_CCM +* [x] AES\_128\_GCM, AES\_256\_GCM +* [x] AES\_128\_GCM\_SIV, AES\_256\_GCM\_SIV +* [x] CHACHA20\_POLY1305 (IETF Version) +* [x] XCHACHA20\_POLY1305 (IETF Version) +* [ ] AES\_128\_OCB\_TAGLEN128, AES\_192\_OCB\_TAGLEN128, AES\_256\_OCB\_TAGLEN128 +* [ ] AES\_SIV\_CMAC\_256, AES\_SIV\_CMAC\_384, AES\_SIV\_CMAC\_512 +* [x] SM4\_GCM, SM4\_CCM + +AEAD 2022 Ciphers ([SIP022](https://github.com/shadowsocks/shadowsocks-org/issues/196)): + +* [x] AEAD2022\_BLAKE3\_AES\_128\_GCM, AEAD2022\_BLAKE3\_AES\_256\_GCM +* [x] AEAD2022\_BLAKE3\_CHACHA20\_POLY1305, AEAD2022\_BLAKE3\_CHACHA8\_POLY1305 diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..4ee82b9 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,18 @@ +edition = "2021" +max_width = 120 +#indent_style = "Visual" +#fn_call_width = 120 +reorder_imports = true +reorder_modules = true +#reorder_imports_in_group = true +#reorder_imported_names = true +condense_wildcard_suffixes = true +#fn_args_layout = "Visual" +#fn_call_style = "Visual" +#chain_indent = "Visual" +normalize_comments = true +use_try_shorthand = true +reorder_impl_items = true +#use_small_heuristics = "Max" +imports_layout = "HorizontalVertical" +imports_granularity = "Crate" diff --git a/src/kind.rs b/src/kind.rs new file mode 100644 index 0000000..a0cd6b5 --- /dev/null +++ b/src/kind.rs @@ -0,0 +1,860 @@ +//! Cipher Kind + +#[cfg(feature = "v1-aead-extra")] +use crate::v1::aeadcipher::{Aes128Ccm, Aes128GcmSiv, Aes256Ccm, Aes256GcmSiv, Sm4Ccm, Sm4Gcm, XChaCha20Poly1305}; +#[cfg(feature = "v1-aead")] +use crate::v1::aeadcipher::{Aes128Gcm, Aes256Gcm, ChaCha20Poly1305}; + +#[cfg(feature = "v1-stream")] +use crate::v1::streamcipher::{ + Aes128Cfb1, + Aes128Cfb128, + Aes128Cfb8, + Aes128Ctr, + Aes128Ofb, + Aes192Cfb1, + Aes192Cfb128, + Aes192Cfb8, + Aes192Ctr, + Aes192Ofb, + Aes256Cfb1, + Aes256Cfb128, + + Aes256Cfb8, + Aes256Ctr, + Aes256Ofb, + Camellia128Cfb1, + Camellia128Cfb128, + Camellia128Cfb8, + Camellia128Ctr, + Camellia128Ofb, + Camellia192Cfb1, + Camellia192Cfb128, + Camellia192Cfb8, + Camellia192Ctr, + Camellia192Ofb, + Camellia256Cfb1, + Camellia256Cfb128, + + Camellia256Cfb8, + Camellia256Ctr, + Camellia256Ofb, + + Chacha20, + Rc4, + Rc4Md5, +}; + +#[cfg(feature = "v2-extra")] +use crate::v2::crypto::ChaCha8Poly1305 as Aead2022ChaCha8Poly1305; +#[cfg(feature = "v2")] +use crate::v2::crypto::{ + Aes128Gcm as Aead2022Aes128Gcm, + Aes256Gcm as Aead2022Aes256Gcm, + ChaCha20Poly1305 as Aead2022ChaCha20Poly1305, +}; + +/// Category of ciphers +#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)] +pub enum CipherCategory { + /// No encryption + None, + /// Stream ciphers is used for OLD ShadowSocks protocol, which uses stream ciphers to encrypt data payloads + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + Stream, + /// AEAD ciphers is used in modern ShadowSocks protocol, which sends data in separate packets + #[cfg(feature = "v1-aead")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-aead")))] + Aead, + /// AEAD ciphers 2022 with enhanced security + #[cfg(feature = "v2")] + #[cfg_attr(docsrs, doc(cfg(feature = "v2")))] + Aead2022, +} + +/// ShadowSocks cipher type +#[allow(non_camel_case_types)] +#[allow(clippy::upper_case_acronyms)] +#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)] +pub enum CipherKind { + NONE, + + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + SS_TABLE, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + SS_RC4_MD5, + + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + AES_128_CTR, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + AES_192_CTR, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + AES_256_CTR, + + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + AES_128_CFB1, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + AES_128_CFB8, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + AES_128_CFB128, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + AES_192_CFB1, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + AES_192_CFB8, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + AES_192_CFB128, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + AES_256_CFB1, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + AES_256_CFB8, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + AES_256_CFB128, + + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + AES_128_OFB, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + AES_192_OFB, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + AES_256_OFB, + + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + CAMELLIA_128_CTR, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + CAMELLIA_192_CTR, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + CAMELLIA_256_CTR, + + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + CAMELLIA_128_CFB1, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + CAMELLIA_128_CFB8, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + CAMELLIA_128_CFB128, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + CAMELLIA_192_CFB1, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + CAMELLIA_192_CFB8, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + CAMELLIA_192_CFB128, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + CAMELLIA_256_CFB1, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + CAMELLIA_256_CFB8, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + CAMELLIA_256_CFB128, + + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + CAMELLIA_128_OFB, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + CAMELLIA_192_OFB, + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + CAMELLIA_256_OFB, + + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + RC4, + // NOTE: IETF 版本 + #[cfg(feature = "v1-stream")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-stream")))] + CHACHA20, + + // AEAD Cipher + #[cfg(feature = "v1-aead")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-aead")))] + /// AEAD_AES_128_GCM + AES_128_GCM, + #[cfg(feature = "v1-aead")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-aead")))] + /// AEAD_AES_256_GCM + AES_256_GCM, + + #[cfg(feature = "v1-aead-extra")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-aead-extra")))] + /// AEAD_AES_128_CCM + AES_128_CCM, + #[cfg(feature = "v1-aead-extra")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-aead-extra")))] + /// AEAD_AES_256_CCM + AES_256_CCM, + + #[cfg(feature = "v1-aead-extra")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-aead-extra")))] + /// AEAD_AES_128_GCM_SIV + AES_128_GCM_SIV, + #[cfg(feature = "v1-aead-extra")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-aead-extra")))] + /// AEAD_AES_256_GCM_SIV + AES_256_GCM_SIV, + + // NOTE: IETF 版本 + #[cfg(feature = "v1-aead")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-aead")))] + /// AEAD_CHACHA20_POLY1305 + CHACHA20_POLY1305, + + #[cfg(feature = "v1-aead-extra")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-aead-extra")))] + /// AEAD_XCHACHA20_POLY1305 + XCHACHA20_POLY1305, + + #[cfg(feature = "v1-aead-extra")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-aead-extra")))] + /// SM4_GCM + SM4_GCM, + #[cfg(feature = "v1-aead-extra")] + #[cfg_attr(docsrs, doc(cfg(feature = "v1-aead-extra")))] + /// SM4_GCM + SM4_CCM, + + #[cfg(feature = "v2")] + #[cfg_attr(docsrs, doc(cfg(feature = "v2")))] + /// 2022-blake3-aes-128-gcm + AEAD2022_BLAKE3_AES_128_GCM, + + #[cfg(feature = "v2")] + #[cfg_attr(docsrs, doc(cfg(feature = "v2")))] + /// 2022-blake3-aes-128-gcm + AEAD2022_BLAKE3_AES_256_GCM, + + #[cfg(feature = "v2")] + #[cfg_attr(docsrs, doc(cfg(feature = "v2")))] + /// 2022-blake3-chacha20-poly1305 + AEAD2022_BLAKE3_CHACHA20_POLY1305, + #[cfg(feature = "v2-extra")] + #[cfg_attr(docsrs, doc(cfg(feature = "v2-extra")))] + /// 2022-blake3-chacha8-poly1305 + AEAD2022_BLAKE3_CHACHA8_POLY1305, +} + +impl CipherKind { + /// The category of the cipher + pub fn category(&self) -> CipherCategory { + #[cfg(feature = "v1-stream")] + if self.is_stream() { + return CipherCategory::Stream; + } + + #[cfg(feature = "v1-aead")] + if self.is_aead() { + return CipherCategory::Aead; + } + + #[cfg(feature = "v2")] + if self.is_aead_2022() { + return CipherCategory::Aead2022; + } + + CipherCategory::None + } + + /// Check if the current cipher is `NONE` + pub fn is_none(&self) -> bool { + matches!(*self, CipherKind::NONE) + } + + /// Check if the current cipher is a stream cipher + #[cfg(feature = "v1-stream")] + #[allow(clippy::match_like_matches_macro)] + pub fn is_stream(&self) -> bool { + use self::CipherKind::*; + + match *self { + SS_TABLE | SS_RC4_MD5 | AES_128_CTR | AES_192_CTR | AES_256_CTR | AES_128_CFB1 | AES_128_CFB8 + | AES_128_CFB128 | AES_192_CFB1 | AES_192_CFB8 | AES_192_CFB128 | AES_256_CFB1 | AES_256_CFB8 + | AES_256_CFB128 | AES_128_OFB | AES_192_OFB | AES_256_OFB | CAMELLIA_128_CTR | CAMELLIA_192_CTR + | CAMELLIA_256_CTR | CAMELLIA_128_CFB1 | CAMELLIA_128_CFB8 | CAMELLIA_128_CFB128 | CAMELLIA_192_CFB1 + | CAMELLIA_192_CFB8 | CAMELLIA_192_CFB128 | CAMELLIA_256_CFB1 | CAMELLIA_256_CFB8 | CAMELLIA_256_CFB128 + | CAMELLIA_128_OFB | CAMELLIA_192_OFB | CAMELLIA_256_OFB | RC4 | CHACHA20 => true, + _ => false, + } + } + + /// Check if the current cipher is an AEAD cipher + #[cfg(feature = "v1-aead")] + pub fn is_aead(&self) -> bool { + use self::CipherKind::*; + + match *self { + AES_128_GCM | AES_256_GCM | CHACHA20_POLY1305 => true, + + #[cfg(feature = "v1-aead-extra")] + AES_128_CCM | AES_256_CCM | AES_128_GCM_SIV | AES_256_GCM_SIV | XCHACHA20_POLY1305 | SM4_GCM | SM4_CCM => { + true + } + + _ => false, + } + } + + #[cfg(feature = "v2")] + pub fn is_aead_2022(&self) -> bool { + use self::CipherKind::*; + + match *self { + AEAD2022_BLAKE3_AES_128_GCM | AEAD2022_BLAKE3_AES_256_GCM | AEAD2022_BLAKE3_CHACHA20_POLY1305 => true, + #[cfg(feature = "v2-extra")] + AEAD2022_BLAKE3_CHACHA8_POLY1305 => true, + _ => false, + } + } + + /// Key length of the cipher + pub fn key_len(&self) -> usize { + use self::CipherKind::*; + + match *self { + NONE => 0, + + #[cfg(feature = "v1-stream")] + SS_TABLE => 0, + #[cfg(feature = "v1-stream")] + SS_RC4_MD5 => Rc4Md5::key_size(), + + #[cfg(feature = "v1-stream")] + AES_128_CTR => Aes128Ctr::KEY_LEN, + #[cfg(feature = "v1-stream")] + AES_192_CTR => Aes192Ctr::KEY_LEN, + #[cfg(feature = "v1-stream")] + AES_256_CTR => Aes256Ctr::KEY_LEN, + + #[cfg(feature = "v1-stream")] + AES_128_CFB1 => Aes128Cfb1::KEY_LEN, + #[cfg(feature = "v1-stream")] + AES_128_CFB8 => Aes128Cfb8::KEY_LEN, + #[cfg(feature = "v1-stream")] + AES_128_CFB128 => Aes128Cfb128::KEY_LEN, + #[cfg(feature = "v1-stream")] + AES_192_CFB1 => Aes192Cfb1::KEY_LEN, + #[cfg(feature = "v1-stream")] + AES_192_CFB8 => Aes192Cfb8::KEY_LEN, + #[cfg(feature = "v1-stream")] + AES_192_CFB128 => Aes192Cfb128::KEY_LEN, + #[cfg(feature = "v1-stream")] + AES_256_CFB1 => Aes256Cfb1::KEY_LEN, + #[cfg(feature = "v1-stream")] + AES_256_CFB8 => Aes256Cfb8::KEY_LEN, + #[cfg(feature = "v1-stream")] + AES_256_CFB128 => Aes256Cfb128::KEY_LEN, + + #[cfg(feature = "v1-stream")] + AES_128_OFB => Aes128Ofb::KEY_LEN, + #[cfg(feature = "v1-stream")] + AES_192_OFB => Aes192Ofb::KEY_LEN, + #[cfg(feature = "v1-stream")] + AES_256_OFB => Aes256Ofb::KEY_LEN, + + #[cfg(feature = "v1-stream")] + CAMELLIA_128_CTR => Camellia128Ctr::KEY_LEN, + #[cfg(feature = "v1-stream")] + CAMELLIA_192_CTR => Camellia192Ctr::KEY_LEN, + #[cfg(feature = "v1-stream")] + CAMELLIA_256_CTR => Camellia256Ctr::KEY_LEN, + #[cfg(feature = "v1-stream")] + CAMELLIA_128_CFB1 => Camellia128Cfb1::KEY_LEN, + #[cfg(feature = "v1-stream")] + CAMELLIA_128_CFB8 => Camellia128Cfb8::KEY_LEN, + #[cfg(feature = "v1-stream")] + CAMELLIA_128_CFB128 => Camellia128Cfb128::KEY_LEN, + #[cfg(feature = "v1-stream")] + CAMELLIA_192_CFB1 => Camellia192Cfb1::KEY_LEN, + #[cfg(feature = "v1-stream")] + CAMELLIA_192_CFB8 => Camellia192Cfb8::KEY_LEN, + #[cfg(feature = "v1-stream")] + CAMELLIA_192_CFB128 => Camellia192Cfb128::KEY_LEN, + #[cfg(feature = "v1-stream")] + CAMELLIA_256_CFB1 => Camellia256Cfb1::KEY_LEN, + #[cfg(feature = "v1-stream")] + CAMELLIA_256_CFB8 => Camellia256Cfb8::KEY_LEN, + #[cfg(feature = "v1-stream")] + CAMELLIA_256_CFB128 => Camellia256Cfb128::KEY_LEN, + + #[cfg(feature = "v1-stream")] + CAMELLIA_128_OFB => Camellia128Ofb::KEY_LEN, + #[cfg(feature = "v1-stream")] + CAMELLIA_192_OFB => Camellia192Ofb::KEY_LEN, + #[cfg(feature = "v1-stream")] + CAMELLIA_256_OFB => Camellia256Ofb::KEY_LEN, + + // NOTE: RC4 密码本身支持 1..256 长度的 Key, + // 但是 SS 这里把 Key 的长度限制在 16. + #[cfg(feature = "v1-stream")] + RC4 => Rc4::key_size(), + #[cfg(feature = "v1-stream")] + CHACHA20 => Chacha20::key_size(), + + // AEAD + #[cfg(feature = "v1-aead")] + AES_128_GCM => Aes128Gcm::key_size(), + #[cfg(feature = "v1-aead")] + AES_256_GCM => Aes256Gcm::key_size(), + + #[cfg(feature = "v1-aead-extra")] + AES_128_CCM => Aes128Ccm::key_size(), + #[cfg(feature = "v1-aead-extra")] + AES_256_CCM => Aes256Ccm::key_size(), + + #[cfg(feature = "v1-aead-extra")] + AES_128_GCM_SIV => Aes128GcmSiv::key_size(), + #[cfg(feature = "v1-aead-extra")] + AES_256_GCM_SIV => Aes256GcmSiv::key_size(), + + #[cfg(feature = "v1-aead")] + CHACHA20_POLY1305 => ChaCha20Poly1305::key_size(), + + #[cfg(feature = "v1-aead-extra")] + XCHACHA20_POLY1305 => XChaCha20Poly1305::key_size(), + + #[cfg(feature = "v1-aead-extra")] + SM4_GCM => Sm4Gcm::key_size(), + #[cfg(feature = "v1-aead-extra")] + SM4_CCM => Sm4Ccm::key_size(), + + #[cfg(feature = "v2")] + AEAD2022_BLAKE3_AES_128_GCM => Aead2022Aes128Gcm::key_size(), + #[cfg(feature = "v2")] + AEAD2022_BLAKE3_AES_256_GCM => Aead2022Aes256Gcm::key_size(), + #[cfg(feature = "v2")] + AEAD2022_BLAKE3_CHACHA20_POLY1305 => Aead2022ChaCha20Poly1305::key_size(), + #[cfg(feature = "v2-extra")] + AEAD2022_BLAKE3_CHACHA8_POLY1305 => Aead2022ChaCha8Poly1305::key_size(), + } + } + + /// Stream Cipher's initializer vector length + #[cfg(feature = "v1-stream")] + pub fn iv_len(&self) -> usize { + use self::CipherKind::*; + + match *self { + NONE => 0, + SS_TABLE => 0, + + SS_RC4_MD5 => Rc4Md5::nonce_size(), + + AES_128_CTR => Aes128Ctr::IV_LEN, + AES_192_CTR => Aes192Ctr::IV_LEN, + AES_256_CTR => Aes256Ctr::IV_LEN, + + AES_128_CFB1 => Aes128Cfb1::IV_LEN, + AES_128_CFB8 => Aes128Cfb8::IV_LEN, + AES_128_CFB128 => Aes128Cfb128::IV_LEN, + AES_192_CFB1 => Aes192Cfb1::IV_LEN, + AES_192_CFB8 => Aes192Cfb8::IV_LEN, + AES_192_CFB128 => Aes192Cfb128::IV_LEN, + AES_256_CFB1 => Aes256Cfb1::IV_LEN, + AES_256_CFB8 => Aes256Cfb8::IV_LEN, + AES_256_CFB128 => Aes256Cfb128::IV_LEN, + + AES_128_OFB => Aes128Ofb::IV_LEN, + AES_192_OFB => Aes192Ofb::IV_LEN, + AES_256_OFB => Aes256Ofb::IV_LEN, + + CAMELLIA_128_CTR => Camellia128Ctr::IV_LEN, + CAMELLIA_192_CTR => Camellia192Ctr::IV_LEN, + CAMELLIA_256_CTR => Camellia256Ctr::IV_LEN, + + CAMELLIA_128_CFB1 => Camellia128Cfb1::IV_LEN, + CAMELLIA_128_CFB8 => Camellia128Cfb8::IV_LEN, + CAMELLIA_128_CFB128 => Camellia128Cfb128::IV_LEN, + CAMELLIA_192_CFB1 => Camellia192Cfb1::IV_LEN, + CAMELLIA_192_CFB8 => Camellia192Cfb8::IV_LEN, + CAMELLIA_192_CFB128 => Camellia192Cfb128::IV_LEN, + CAMELLIA_256_CFB1 => Camellia256Cfb1::IV_LEN, + CAMELLIA_256_CFB8 => Camellia256Cfb8::IV_LEN, + CAMELLIA_256_CFB128 => Camellia256Cfb128::IV_LEN, + + CAMELLIA_128_OFB => Camellia128Ofb::IV_LEN, + CAMELLIA_192_OFB => Camellia192Ofb::IV_LEN, + CAMELLIA_256_OFB => Camellia256Ofb::IV_LEN, + + RC4 => Rc4::nonce_size(), + CHACHA20 => Chacha20::nonce_size(), + + _ => panic!("only support Stream ciphers"), + } + } + + /// AEAD Cipher's TAG length + #[cfg(any(feature = "v1-aead", feature = "v2"))] + pub fn tag_len(&self) -> usize { + use self::CipherKind::*; + + match *self { + AES_128_GCM => Aes128Gcm::tag_size(), + AES_256_GCM => Aes256Gcm::tag_size(), + + #[cfg(feature = "v1-aead-extra")] + AES_128_GCM_SIV => Aes128GcmSiv::tag_size(), + #[cfg(feature = "v1-aead-extra")] + AES_256_GCM_SIV => Aes256GcmSiv::tag_size(), + + #[cfg(feature = "v1-aead-extra")] + AES_128_CCM => Aes128Ccm::tag_size(), + #[cfg(feature = "v1-aead-extra")] + AES_256_CCM => Aes256Ccm::tag_size(), + + CHACHA20_POLY1305 => ChaCha20Poly1305::tag_size(), + + #[cfg(feature = "v1-aead-extra")] + XCHACHA20_POLY1305 => XChaCha20Poly1305::tag_size(), + + #[cfg(feature = "v1-aead-extra")] + SM4_GCM => Sm4Gcm::tag_size(), + #[cfg(feature = "v1-aead-extra")] + SM4_CCM => Sm4Ccm::tag_size(), + + #[cfg(feature = "v2")] + AEAD2022_BLAKE3_AES_128_GCM => Aead2022Aes128Gcm::tag_size(), + #[cfg(feature = "v2")] + AEAD2022_BLAKE3_AES_256_GCM => Aead2022Aes256Gcm::tag_size(), + #[cfg(feature = "v2")] + AEAD2022_BLAKE3_CHACHA20_POLY1305 => Aead2022ChaCha20Poly1305::tag_size(), + #[cfg(feature = "v2-extra")] + AEAD2022_BLAKE3_CHACHA8_POLY1305 => Aead2022ChaCha8Poly1305::tag_size(), + + _ => panic!("only support AEAD ciphers"), + } + } + + /// AEAD Cipher's SALT length + #[cfg(any(feature = "v1-aead", feature = "v2"))] + pub fn salt_len(&self) -> usize { + #[cfg(feature = "v1-aead")] + if self.is_aead() { + return self.key_len(); + } + + #[cfg(feature = "v2")] + if self.is_aead_2022() { + return self.key_len(); + } + + panic!("only support AEAD ciphers"); + } + + /// AEAD Cipher's nonce length + #[cfg(feature = "v2")] + pub fn nonce_len(&self) -> usize { + #[cfg(feature = "v2-extra")] + use crate::v2::udp::ChaCha8Poly1305Cipher; + use crate::v2::udp::{AesGcmCipher, ChaCha20Poly1305Cipher}; + + match *self { + CipherKind::AEAD2022_BLAKE3_AES_128_GCM | CipherKind::AEAD2022_BLAKE3_AES_256_GCM => { + AesGcmCipher::nonce_size() + } + CipherKind::AEAD2022_BLAKE3_CHACHA20_POLY1305 => ChaCha20Poly1305Cipher::nonce_size(), + #[cfg(feature = "v2-extra")] + CipherKind::AEAD2022_BLAKE3_CHACHA8_POLY1305 => ChaCha8Poly1305Cipher::nonce_size(), + _ => panic!("only support AEAD 2022 ciphers"), + } + } +} + +impl core::fmt::Display for CipherKind { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.write_str(match *self { + CipherKind::NONE => "none", + + #[cfg(feature = "v1-stream")] + CipherKind::SS_TABLE => "table", + #[cfg(feature = "v1-stream")] + CipherKind::SS_RC4_MD5 => "rc4-md5", + + #[cfg(feature = "v1-stream")] + CipherKind::AES_128_CTR => "aes-128-ctr", + #[cfg(feature = "v1-stream")] + CipherKind::AES_192_CTR => "aes-192-ctr", + #[cfg(feature = "v1-stream")] + CipherKind::AES_256_CTR => "aes-256-ctr", + + #[cfg(feature = "v1-stream")] + CipherKind::AES_128_CFB128 => "aes-128-cfb", + #[cfg(feature = "v1-stream")] + CipherKind::AES_128_CFB1 => "aes-128-cfb1", + #[cfg(feature = "v1-stream")] + CipherKind::AES_128_CFB8 => "aes-128-cfb8", + + #[cfg(feature = "v1-stream")] + CipherKind::AES_192_CFB128 => "aes-192-cfb", + #[cfg(feature = "v1-stream")] + CipherKind::AES_192_CFB1 => "aes-192-cfb1", + #[cfg(feature = "v1-stream")] + CipherKind::AES_192_CFB8 => "aes-192-cfb8", + + #[cfg(feature = "v1-stream")] + CipherKind::AES_256_CFB128 => "aes-256-cfb", + #[cfg(feature = "v1-stream")] + CipherKind::AES_256_CFB1 => "aes-256-cfb1", + #[cfg(feature = "v1-stream")] + CipherKind::AES_256_CFB8 => "aes-256-cfb8", + + #[cfg(feature = "v1-stream")] + CipherKind::AES_128_OFB => "aes-128-ofb", + #[cfg(feature = "v1-stream")] + CipherKind::AES_192_OFB => "aes-192-ofb", + #[cfg(feature = "v1-stream")] + CipherKind::AES_256_OFB => "aes-256-ofb", + + #[cfg(feature = "v1-stream")] + CipherKind::CAMELLIA_128_CTR => "camellia-128-ctr", + #[cfg(feature = "v1-stream")] + CipherKind::CAMELLIA_192_CTR => "camellia-192-ctr", + #[cfg(feature = "v1-stream")] + CipherKind::CAMELLIA_256_CTR => "camellia-256-ctr", + + #[cfg(feature = "v1-stream")] + CipherKind::CAMELLIA_128_CFB128 => "camellia-128-cfb", + #[cfg(feature = "v1-stream")] + CipherKind::CAMELLIA_128_CFB1 => "camellia-128-cfb1", + #[cfg(feature = "v1-stream")] + CipherKind::CAMELLIA_128_CFB8 => "camellia-128-cfb8", + + #[cfg(feature = "v1-stream")] + CipherKind::CAMELLIA_192_CFB128 => "camellia-192-cfb", + #[cfg(feature = "v1-stream")] + CipherKind::CAMELLIA_192_CFB1 => "camellia-192-cfb1", + #[cfg(feature = "v1-stream")] + CipherKind::CAMELLIA_192_CFB8 => "camellia-192-cfb8", + + #[cfg(feature = "v1-stream")] + CipherKind::CAMELLIA_256_CFB128 => "camellia-256-cfb", + #[cfg(feature = "v1-stream")] + CipherKind::CAMELLIA_256_CFB1 => "camellia-256-cfb1", + #[cfg(feature = "v1-stream")] + CipherKind::CAMELLIA_256_CFB8 => "camellia-256-cfb8", + + #[cfg(feature = "v1-stream")] + CipherKind::CAMELLIA_128_OFB => "camellia-128-ofb", + #[cfg(feature = "v1-stream")] + CipherKind::CAMELLIA_192_OFB => "camellia-192-ofb", + #[cfg(feature = "v1-stream")] + CipherKind::CAMELLIA_256_OFB => "camellia-256-ofb", + + #[cfg(feature = "v1-stream")] + CipherKind::RC4 => "rc4", + #[cfg(feature = "v1-stream")] + CipherKind::CHACHA20 => "chacha20-ietf", + + #[cfg(feature = "v1-aead")] + CipherKind::AES_128_GCM => "aes-128-gcm", + #[cfg(feature = "v1-aead")] + CipherKind::AES_256_GCM => "aes-256-gcm", + + #[cfg(feature = "v1-aead-extra")] + CipherKind::AES_128_CCM => "aes-128-ccm", + #[cfg(feature = "v1-aead-extra")] + CipherKind::AES_256_CCM => "aes-256-ccm", + + #[cfg(feature = "v1-aead-extra")] + CipherKind::AES_128_GCM_SIV => "aes-128-gcm-siv", + #[cfg(feature = "v1-aead-extra")] + CipherKind::AES_256_GCM_SIV => "aes-256-gcm-siv", + + #[cfg(feature = "v1-aead")] + CipherKind::CHACHA20_POLY1305 => "chacha20-ietf-poly1305", + + #[cfg(feature = "v1-aead-extra")] + CipherKind::XCHACHA20_POLY1305 => "xchacha20-ietf-poly1305", + + #[cfg(feature = "v1-aead-extra")] + CipherKind::SM4_GCM => "sm4-gcm", + #[cfg(feature = "v1-aead-extra")] + CipherKind::SM4_CCM => "sm4-ccm", + + #[cfg(feature = "v2")] + CipherKind::AEAD2022_BLAKE3_AES_128_GCM => "2022-blake3-aes-128-gcm", + #[cfg(feature = "v2")] + CipherKind::AEAD2022_BLAKE3_AES_256_GCM => "2022-blake3-aes-256-gcm", + #[cfg(feature = "v2")] + CipherKind::AEAD2022_BLAKE3_CHACHA20_POLY1305 => "2022-blake3-chacha20-poly1305", + #[cfg(feature = "v2-extra")] + CipherKind::AEAD2022_BLAKE3_CHACHA8_POLY1305 => "2022-blake3-chacha8-poly1305", + }) + } +} + +/// Error while parsing `CipherKind` from string +#[derive(Debug, Clone)] +pub struct ParseCipherKindError; + +impl core::fmt::Display for ParseCipherKindError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("invalid CipherKind") + } +} + +impl core::str::FromStr for CipherKind { + type Err = ParseCipherKindError; + + fn from_str(s: &str) -> Result { + use self::CipherKind::*; + + match s.to_lowercase().as_str() { + "plain" | "none" => Ok(NONE), + + #[cfg(feature = "v1-stream")] + "table" | "" => Ok(SS_TABLE), + #[cfg(feature = "v1-stream")] + "rc4-md5" => Ok(SS_RC4_MD5), + + #[cfg(feature = "v1-stream")] + "aes-128-ctr" => Ok(AES_128_CTR), + #[cfg(feature = "v1-stream")] + "aes-192-ctr" => Ok(AES_192_CTR), + #[cfg(feature = "v1-stream")] + "aes-256-ctr" => Ok(AES_256_CTR), + + #[cfg(feature = "v1-stream")] + "aes-128-cfb" => Ok(AES_128_CFB128), + #[cfg(feature = "v1-stream")] + "aes-128-cfb1" => Ok(AES_128_CFB1), + #[cfg(feature = "v1-stream")] + "aes-128-cfb8" => Ok(AES_128_CFB8), + #[cfg(feature = "v1-stream")] + "aes-128-cfb128" => Ok(AES_128_CFB128), + + #[cfg(feature = "v1-stream")] + "aes-192-cfb" => Ok(AES_192_CFB128), + #[cfg(feature = "v1-stream")] + "aes-192-cfb1" => Ok(AES_192_CFB1), + #[cfg(feature = "v1-stream")] + "aes-192-cfb8" => Ok(AES_192_CFB8), + #[cfg(feature = "v1-stream")] + "aes-192-cfb128" => Ok(AES_192_CFB128), + + #[cfg(feature = "v1-stream")] + "aes-256-cfb" => Ok(AES_256_CFB128), + #[cfg(feature = "v1-stream")] + "aes-256-cfb1" => Ok(AES_256_CFB1), + #[cfg(feature = "v1-stream")] + "aes-256-cfb8" => Ok(AES_256_CFB8), + #[cfg(feature = "v1-stream")] + "aes-256-cfb128" => Ok(AES_256_CFB128), + + #[cfg(feature = "v1-stream")] + "aes-128-ofb" => Ok(AES_128_OFB), + #[cfg(feature = "v1-stream")] + "aes-192-ofb" => Ok(AES_192_OFB), + #[cfg(feature = "v1-stream")] + "aes-256-ofb" => Ok(AES_256_OFB), + + #[cfg(feature = "v1-stream")] + "camellia-128-ctr" => Ok(CAMELLIA_128_CTR), + #[cfg(feature = "v1-stream")] + "camellia-192-ctr" => Ok(CAMELLIA_192_CTR), + #[cfg(feature = "v1-stream")] + "camellia-256-ctr" => Ok(CAMELLIA_256_CTR), + + #[cfg(feature = "v1-stream")] + "camellia-128-cfb" => Ok(CAMELLIA_128_CFB128), + #[cfg(feature = "v1-stream")] + "camellia-128-cfb1" => Ok(CAMELLIA_128_CFB1), + #[cfg(feature = "v1-stream")] + "camellia-128-cfb8" => Ok(CAMELLIA_128_CFB8), + #[cfg(feature = "v1-stream")] + "camellia-128-cfb128" => Ok(CAMELLIA_128_CFB128), + + #[cfg(feature = "v1-stream")] + "camellia-192-cfb" => Ok(CAMELLIA_192_CFB128), + #[cfg(feature = "v1-stream")] + "camellia-192-cfb1" => Ok(CAMELLIA_192_CFB1), + #[cfg(feature = "v1-stream")] + "camellia-192-cfb8" => Ok(CAMELLIA_192_CFB8), + #[cfg(feature = "v1-stream")] + "camellia-192-cfb128" => Ok(CAMELLIA_192_CFB128), + + #[cfg(feature = "v1-stream")] + "camellia-256-cfb" => Ok(CAMELLIA_256_CFB128), + #[cfg(feature = "v1-stream")] + "camellia-256-cfb1" => Ok(CAMELLIA_256_CFB1), + #[cfg(feature = "v1-stream")] + "camellia-256-cfb8" => Ok(CAMELLIA_256_CFB8), + #[cfg(feature = "v1-stream")] + "camellia-256-cfb128" => Ok(CAMELLIA_256_CFB128), + + #[cfg(feature = "v1-stream")] + "camellia-128-ofb" => Ok(CAMELLIA_128_OFB), + #[cfg(feature = "v1-stream")] + "camellia-192-ofb" => Ok(CAMELLIA_192_OFB), + #[cfg(feature = "v1-stream")] + "camellia-256-ofb" => Ok(CAMELLIA_256_OFB), + + #[cfg(feature = "v1-stream")] + "rc4" => Ok(RC4), + #[cfg(feature = "v1-stream")] + "chacha20-ietf" => Ok(CHACHA20), + + // AEAD Ciphers + #[cfg(feature = "v1-aead")] + "aes-128-gcm" => Ok(AES_128_GCM), + #[cfg(feature = "v1-aead")] + "aes-256-gcm" => Ok(AES_256_GCM), + + #[cfg(feature = "v1-aead-extra")] + "aes-128-ccm" => Ok(AES_128_CCM), + #[cfg(feature = "v1-aead-extra")] + "aes-256-ccm" => Ok(AES_256_CCM), + + #[cfg(feature = "v1-aead-extra")] + "aes-128-gcm-siv" => Ok(AES_128_GCM_SIV), + #[cfg(feature = "v1-aead-extra")] + "aes-256-gcm-siv" => Ok(AES_256_GCM_SIV), + + #[cfg(feature = "v1-aead")] + "chacha20-ietf-poly1305" => Ok(CHACHA20_POLY1305), + + #[cfg(feature = "v1-aead-extra")] + "xchacha20-ietf-poly1305" => Ok(XCHACHA20_POLY1305), + + #[cfg(feature = "v1-aead-extra")] + "sm4-gcm" => Ok(SM4_GCM), + #[cfg(feature = "v1-aead-extra")] + "sm4-ccm" => Ok(SM4_CCM), + + #[cfg(feature = "v2")] + "2022-blake3-aes-128-gcm" => Ok(AEAD2022_BLAKE3_AES_128_GCM), + #[cfg(feature = "v2")] + "2022-blake3-aes-256-gcm" => Ok(AEAD2022_BLAKE3_AES_256_GCM), + #[cfg(feature = "v2")] + "2022-blake3-chacha20-poly1305" => Ok(AEAD2022_BLAKE3_CHACHA20_POLY1305), + #[cfg(feature = "v2-extra")] + "2022-blake3-chacha8-poly1305" => Ok(AEAD2022_BLAKE3_CHACHA8_POLY1305), + + _ => Err(ParseCipherKindError), + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a84a232 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,136 @@ +//! Shadowsocks Cipher implementation + +#![cfg_attr(docsrs, feature(doc_cfg))] + +#[cfg(feature = "v1")] +#[cfg_attr(docsrs, doc(cfg(feature = "v1")))] +pub mod v1; + +#[cfg(feature = "v2")] +#[cfg_attr(docsrs, doc(cfg(feature = "v2")))] +pub mod v2; + +pub mod kind; + +pub use self::kind::{CipherCategory, CipherKind}; + +/// Get available ciphers in string representation +/// +/// Commonly used for checking users' configuration input +pub const fn available_ciphers() -> &'static [&'static str] { + &[ + "plain", + "none", + #[cfg(feature = "v1-stream")] + "table", + #[cfg(feature = "v1-stream")] + "rc4-md5", + // Stream Ciphers + #[cfg(feature = "v1-stream")] + "aes-128-ctr", + #[cfg(feature = "v1-stream")] + "aes-192-ctr", + #[cfg(feature = "v1-stream")] + "aes-256-ctr", + #[cfg(feature = "v1-stream")] + "aes-128-cfb", + #[cfg(feature = "v1-stream")] + "aes-128-cfb1", + #[cfg(feature = "v1-stream")] + "aes-128-cfb8", + #[cfg(feature = "v1-stream")] + "aes-128-cfb128", + #[cfg(feature = "v1-stream")] + "aes-192-cfb", + #[cfg(feature = "v1-stream")] + "aes-192-cfb1", + #[cfg(feature = "v1-stream")] + "aes-192-cfb8", + #[cfg(feature = "v1-stream")] + "aes-192-cfb128", + #[cfg(feature = "v1-stream")] + "aes-256-cfb", + #[cfg(feature = "v1-stream")] + "aes-256-cfb1", + #[cfg(feature = "v1-stream")] + "aes-256-cfb8", + #[cfg(feature = "v1-stream")] + "aes-256-cfb128", + #[cfg(feature = "v1-stream")] + "aes-128-ofb", + #[cfg(feature = "v1-stream")] + "aes-192-ofb", + #[cfg(feature = "v1-stream")] + "aes-256-ofb", + #[cfg(feature = "v1-stream")] + "camellia-128-ctr", + #[cfg(feature = "v1-stream")] + "camellia-192-ctr", + #[cfg(feature = "v1-stream")] + "camellia-256-ctr", + #[cfg(feature = "v1-stream")] + "camellia-128-cfb", + #[cfg(feature = "v1-stream")] + "camellia-128-cfb1", + #[cfg(feature = "v1-stream")] + "camellia-128-cfb8", + #[cfg(feature = "v1-stream")] + "camellia-128-cfb128", + #[cfg(feature = "v1-stream")] + "camellia-192-cfb", + #[cfg(feature = "v1-stream")] + "camellia-192-cfb1", + #[cfg(feature = "v1-stream")] + "camellia-192-cfb8", + #[cfg(feature = "v1-stream")] + "camellia-192-cfb128", + #[cfg(feature = "v1-stream")] + "camellia-256-cfb", + #[cfg(feature = "v1-stream")] + "camellia-256-cfb1", + #[cfg(feature = "v1-stream")] + "camellia-256-cfb8", + #[cfg(feature = "v1-stream")] + "camellia-256-cfb128", + #[cfg(feature = "v1-stream")] + "camellia-128-ofb", + #[cfg(feature = "v1-stream")] + "camellia-192-ofb", + #[cfg(feature = "v1-stream")] + "camellia-256-ofb", + #[cfg(feature = "v1-stream")] + "rc4", + #[cfg(feature = "v1-stream")] + "chacha20-ietf", + // AEAD Ciphers + #[cfg(feature = "v1-aead")] + "aes-128-gcm", + #[cfg(feature = "v1-aead")] + "aes-256-gcm", + #[cfg(feature = "v1-aead")] + "chacha20-ietf-poly1305", + #[cfg(feature = "v1-aead-extra")] + "aes-128-ccm", + #[cfg(feature = "v1-aead-extra")] + "aes-256-ccm", + #[cfg(feature = "v1-aead-extra")] + "aes-128-gcm-siv", + #[cfg(feature = "v1-aead-extra")] + "aes-256-gcm-siv", + #[cfg(feature = "v1-aead-extra")] + "xchacha20-ietf-poly1305", + // #[cfg(feature = "v1-aead-extra")] + // "sm4-gcm", + // #[cfg(feature = "v1-aead-extra")] + // "sm4-ccm", + // AEAD 2022 Ciphers + #[cfg(feature = "v2")] + "2022-blake3-aes-128-gcm", + #[cfg(feature = "v2")] + "2022-blake3-aes-256-gcm", + #[cfg(feature = "v2")] + "2022-blake3-chacha20-poly1305", + #[cfg(feature = "v2-extra")] + "2022-blake3-chacha8-poly1305", + ] +} diff --git a/src/v1/aeadcipher/aes_ccm.rs b/src/v1/aeadcipher/aes_ccm.rs new file mode 100644 index 0000000..1c3a7ad --- /dev/null +++ b/src/v1/aeadcipher/aes_ccm.rs @@ -0,0 +1,86 @@ +use aes::{Aes128, Aes256}; +use ccm::{ + aead::{generic_array::typenum::Unsigned, AeadCore, AeadInPlace, KeyInit, KeySizeUser}, + consts::{U12, U16}, + Ccm, + Nonce, + Tag, +}; + +pub struct Aes128Ccm(Ccm); + +impl Aes128Ccm { + pub fn new(key: &[u8]) -> Aes128Ccm { + Aes128Ccm(Ccm::new_from_slice(key).expect("Aes128Ccm")) + } + + pub fn key_size() -> usize { + as KeySizeUser>::KeySize::to_usize() + } + + pub fn nonce_size() -> usize { + as AeadCore>::NonceSize::to_usize() + } + + pub fn tag_size() -> usize { + as AeadCore>::TagSize::to_usize() + } + + pub fn encrypt(&self, nonce: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + let nonce = Nonce::from_slice(nonce); + let (plaintext, out_tag) = + plaintext_in_ciphertext_out.split_at_mut(plaintext_in_ciphertext_out.len() - Self::tag_size()); + let tag = self + .0 + .encrypt_in_place_detached(nonce, &[], plaintext) + .expect("AES_128_CCM encrypt"); + out_tag.copy_from_slice(tag.as_slice()) + } + + pub fn decrypt(&self, nonce: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + let nonce = Nonce::from_slice(nonce); + let (ciphertext, in_tag) = + ciphertext_in_plaintext_out.split_at_mut(ciphertext_in_plaintext_out.len() - Self::tag_size()); + let in_tag = Tag::from_slice(in_tag); + self.0.decrypt_in_place_detached(nonce, &[], ciphertext, in_tag).is_ok() + } +} + +pub struct Aes256Ccm(Ccm); + +impl Aes256Ccm { + pub fn new(key: &[u8]) -> Aes256Ccm { + Aes256Ccm(Ccm::new_from_slice(key).expect("Aes256Ccm")) + } + + pub fn key_size() -> usize { + as KeySizeUser>::KeySize::to_usize() + } + + pub fn nonce_size() -> usize { + as AeadCore>::NonceSize::to_usize() + } + + pub fn tag_size() -> usize { + as AeadCore>::TagSize::to_usize() + } + + pub fn encrypt(&mut self, nonce: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + let nonce = Nonce::from_slice(nonce); + let (plaintext, out_tag) = + plaintext_in_ciphertext_out.split_at_mut(plaintext_in_ciphertext_out.len() - Self::tag_size()); + let tag = self + .0 + .encrypt_in_place_detached(nonce, &[], plaintext) + .expect("AES_256_CCM encrypt"); + out_tag.copy_from_slice(tag.as_slice()) + } + + pub fn decrypt(&mut self, nonce: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + let nonce = Nonce::from_slice(nonce); + let (ciphertext, in_tag) = + ciphertext_in_plaintext_out.split_at_mut(ciphertext_in_plaintext_out.len() - Self::tag_size()); + let in_tag = Tag::from_slice(in_tag); + self.0.decrypt_in_place_detached(nonce, &[], ciphertext, in_tag).is_ok() + } +} diff --git a/src/v1/aeadcipher/aes_gcm.rs b/src/v1/aeadcipher/aes_gcm.rs new file mode 100644 index 0000000..07a11e7 --- /dev/null +++ b/src/v1/aeadcipher/aes_gcm.rs @@ -0,0 +1,147 @@ +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(feature = "ring")] { + use std::convert::{AsMut, AsRef}; + + pub use ring_compat::aead::{Aes128Gcm as CryptoAes128Gcm, Aes256Gcm as CryptoAes256Gcm}; + use ring_compat::{ + aead::{AeadCore, AeadInPlace, Buffer, Error as AeadError, KeySizeUser, KeyInit}, + generic_array::{typenum::Unsigned, GenericArray}, + }; + + type Key = GenericArray::KeySize>; + type Nonce = GenericArray; + + struct SliceBuffer<'a>(&'a mut [u8]); + + impl AsRef<[u8]> for SliceBuffer<'_> { + fn as_ref(&self) -> &[u8] { + self.0 + } + } + + impl AsMut<[u8]> for SliceBuffer<'_> { + fn as_mut(&mut self) -> &mut [u8] { + self.0 + } + } + + impl Buffer for SliceBuffer<'_> { + fn extend_from_slice(&mut self, _other: &[u8]) -> Result<(), AeadError> { + unimplemented!("not used in decrypt_in_place") + } + + fn truncate(&mut self, _len: usize) {} + } + } else { + use aes_gcm::{ + aead::{generic_array::typenum::Unsigned, AeadCore, AeadInPlace, KeySizeUser, KeyInit}, + Key, + Nonce, + Tag, + }; + pub use aes_gcm::{Aes128Gcm as CryptoAes128Gcm, Aes256Gcm as CryptoAes256Gcm}; + } +} + +pub struct Aes128Gcm(CryptoAes128Gcm); + +impl Aes128Gcm { + pub fn new(key: &[u8]) -> Aes128Gcm { + let key = Key::::from_slice(key); + Aes128Gcm(CryptoAes128Gcm::new(key)) + } + + pub fn key_size() -> usize { + ::KeySize::to_usize() + } + + pub fn nonce_size() -> usize { + ::NonceSize::to_usize() + } + + pub fn tag_size() -> usize { + ::TagSize::to_usize() + } + + pub fn encrypt(&self, nonce: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + let nonce = Nonce::from_slice(nonce); + let (plaintext, out_tag) = + plaintext_in_ciphertext_out.split_at_mut(plaintext_in_ciphertext_out.len() - Self::tag_size()); + let tag = self + .0 + .encrypt_in_place_detached(nonce, &[], plaintext) + .expect("AES_128_GCM encrypt"); + out_tag.copy_from_slice(tag.as_slice()) + } + + pub fn decrypt(&self, nonce: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + let nonce = Nonce::from_slice(nonce); + + cfg_if! { + if #[cfg(feature = "ring")] { + // ring-compat marked decrypt_in_place_detached as unimplemented. + // But AES_128_GCM actually expects tag in the back. So it is safe to use `decrypt_in_place`. + + let mut buffer = SliceBuffer(ciphertext_in_plaintext_out); + self.0.decrypt_in_place(nonce, &[], &mut buffer).is_ok() + } else { + let (ciphertext, in_tag) = + ciphertext_in_plaintext_out.split_at_mut(ciphertext_in_plaintext_out.len() - Self::tag_size()); + let in_tag = Tag::from_slice(in_tag); + self.0.decrypt_in_place_detached(nonce, &[], ciphertext, in_tag).is_ok() + } + } + } +} + +pub struct Aes256Gcm(CryptoAes256Gcm); + +impl Aes256Gcm { + pub fn new(key: &[u8]) -> Aes256Gcm { + let key = Key::::from_slice(key); + Aes256Gcm(CryptoAes256Gcm::new(key)) + } + + pub fn key_size() -> usize { + ::KeySize::to_usize() + } + + pub fn nonce_size() -> usize { + ::NonceSize::to_usize() + } + + pub fn tag_size() -> usize { + ::TagSize::to_usize() + } + + pub fn encrypt(&self, nonce: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + let nonce = Nonce::from_slice(nonce); + let (plaintext, out_tag) = + plaintext_in_ciphertext_out.split_at_mut(plaintext_in_ciphertext_out.len() - Self::tag_size()); + let tag = self + .0 + .encrypt_in_place_detached(nonce, &[], plaintext) + .expect("AES_256_GCM encrypt"); + out_tag.copy_from_slice(tag.as_slice()) + } + + pub fn decrypt(&self, nonce: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + let nonce = Nonce::from_slice(nonce); + cfg_if! { + if #[cfg(feature = "ring")] { + // ring-compat marked decrypt_in_place_detached as unimplemented. + // But AES_256_GCM actually expects tag in the back. So it is safe to use `decrypt_in_place`. + + let mut buffer = SliceBuffer(ciphertext_in_plaintext_out); + self.0.decrypt_in_place(nonce, &[], &mut buffer).is_ok() + } else { + let (ciphertext, in_tag) = + ciphertext_in_plaintext_out.split_at_mut(ciphertext_in_plaintext_out.len() - Self::tag_size()); + let in_tag = Tag::from_slice(in_tag); + self.0.decrypt_in_place_detached(nonce, &[], ciphertext, in_tag).is_ok() + } + } + } +} diff --git a/src/v1/aeadcipher/aes_gcm_siv.rs b/src/v1/aeadcipher/aes_gcm_siv.rs new file mode 100644 index 0000000..4f07f41 --- /dev/null +++ b/src/v1/aeadcipher/aes_gcm_siv.rs @@ -0,0 +1,88 @@ +use aes_gcm_siv::{ + aead::{generic_array::typenum::Unsigned, AeadCore, AeadInPlace, KeyInit, KeySizeUser}, + Aes128GcmSiv as CryptoAes128GcmSiv, + Aes256GcmSiv as CryptoAes256GcmSiv, + Key, + Nonce, + Tag, +}; + +pub struct Aes128GcmSiv(CryptoAes128GcmSiv); + +impl Aes128GcmSiv { + pub fn new(key: &[u8]) -> Aes128GcmSiv { + let key = Key::::from_slice(key); + Aes128GcmSiv(CryptoAes128GcmSiv::new(key)) + } + + pub fn key_size() -> usize { + ::KeySize::to_usize() + } + + pub fn nonce_size() -> usize { + ::NonceSize::to_usize() + } + + pub fn tag_size() -> usize { + ::TagSize::to_usize() + } + + pub fn encrypt(&self, nonce: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + let nonce = Nonce::from_slice(nonce); + let (plaintext, out_tag) = + plaintext_in_ciphertext_out.split_at_mut(plaintext_in_ciphertext_out.len() - Self::tag_size()); + let tag = self + .0 + .encrypt_in_place_detached(nonce, &[], plaintext) + .expect("AES_128_GCM_SIV encrypt"); + out_tag.copy_from_slice(tag.as_slice()) + } + + pub fn decrypt(&self, nonce: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + let nonce = Nonce::from_slice(nonce); + let (ciphertext, in_tag) = + ciphertext_in_plaintext_out.split_at_mut(ciphertext_in_plaintext_out.len() - Self::tag_size()); + let in_tag = Tag::from_slice(in_tag); + self.0.decrypt_in_place_detached(nonce, &[], ciphertext, in_tag).is_ok() + } +} + +pub struct Aes256GcmSiv(CryptoAes256GcmSiv); + +impl Aes256GcmSiv { + pub fn new(key: &[u8]) -> Aes256GcmSiv { + let key = Key::::from_slice(key); + Aes256GcmSiv(CryptoAes256GcmSiv::new(key)) + } + + pub fn key_size() -> usize { + ::KeySize::to_usize() + } + + pub fn nonce_size() -> usize { + ::NonceSize::to_usize() + } + + pub fn tag_size() -> usize { + ::TagSize::to_usize() + } + + pub fn encrypt(&mut self, nonce: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + let nonce = Nonce::from_slice(nonce); + let (plaintext, out_tag) = + plaintext_in_ciphertext_out.split_at_mut(plaintext_in_ciphertext_out.len() - Self::tag_size()); + let tag = self + .0 + .encrypt_in_place_detached(nonce, &[], plaintext) + .expect("AES_256_GCM_SIV encrypt"); + out_tag.copy_from_slice(tag.as_slice()) + } + + pub fn decrypt(&mut self, nonce: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + let nonce = Nonce::from_slice(nonce); + let (ciphertext, in_tag) = + ciphertext_in_plaintext_out.split_at_mut(ciphertext_in_plaintext_out.len() - Self::tag_size()); + let in_tag = Tag::from_slice(in_tag); + self.0.decrypt_in_place_detached(nonce, &[], ciphertext, in_tag).is_ok() + } +} diff --git a/src/v1/aeadcipher/chacha20_poly1305.rs b/src/v1/aeadcipher/chacha20_poly1305.rs new file mode 100644 index 0000000..4464532 --- /dev/null +++ b/src/v1/aeadcipher/chacha20_poly1305.rs @@ -0,0 +1,97 @@ +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(feature = "ring")] { + use std::convert::{AsMut, AsRef}; + + pub use ring_compat::aead::{ChaCha20Poly1305 as CryptoChaCha20Poly1305}; + use ring_compat::{ + aead::{AeadCore, AeadInPlace, Buffer, Error as AeadError, KeySizeUser, KeyInit}, + generic_array::{typenum::Unsigned, GenericArray}, + }; + + type Key = GenericArray; + type Nonce = GenericArray; + + struct SliceBuffer<'a>(&'a mut [u8]); + + impl AsRef<[u8]> for SliceBuffer<'_> { + fn as_ref(&self) -> &[u8] { + self.0 + } + } + + impl AsMut<[u8]> for SliceBuffer<'_> { + fn as_mut(&mut self) -> &mut [u8] { + self.0 + } + } + + impl Buffer for SliceBuffer<'_> { + fn extend_from_slice(&mut self, _other: &[u8]) -> Result<(), AeadError> { + unimplemented!("not used in decrypt_in_place") + } + + fn truncate(&mut self, _len: usize) {} + } + } else { + pub use chacha20poly1305::ChaCha20Poly1305 as CryptoChaCha20Poly1305; + use chacha20poly1305::{ + aead::{generic_array::typenum::Unsigned, AeadCore, AeadInPlace, KeySizeUser, KeyInit}, + Key, + Nonce, + Tag, + }; + } +} + +pub struct ChaCha20Poly1305(CryptoChaCha20Poly1305); + +impl ChaCha20Poly1305 { + pub fn new(key: &[u8]) -> ChaCha20Poly1305 { + let key = Key::from_slice(key); + ChaCha20Poly1305(CryptoChaCha20Poly1305::new(key)) + } + + pub fn key_size() -> usize { + ::KeySize::to_usize() + } + + pub fn nonce_size() -> usize { + ::NonceSize::to_usize() + } + + pub fn tag_size() -> usize { + ::TagSize::to_usize() + } + + pub fn encrypt(&self, nonce: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + let nonce = Nonce::from_slice(nonce); + let (plaintext, out_tag) = + plaintext_in_ciphertext_out.split_at_mut(plaintext_in_ciphertext_out.len() - Self::tag_size()); + let tag = self + .0 + .encrypt_in_place_detached(nonce, &[], plaintext) + .expect("CHACHA20_POLY1305 encrypt"); + out_tag.copy_from_slice(tag.as_slice()) + } + + pub fn decrypt(&self, nonce: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + let nonce = Nonce::from_slice(nonce); + + cfg_if! { + if #[cfg(feature = "ring")] { + // ring-compat marked decrypt_in_place_detached as unimplemented. + // But CHACHA20_POLY1305 actually expects tag in the back. So it is safe to use `decrypt_in_place`. + + let mut buffer = SliceBuffer(ciphertext_in_plaintext_out); + self.0.decrypt_in_place(nonce, &[], &mut buffer).is_ok() + } else { + let (ciphertext, in_tag) = + ciphertext_in_plaintext_out.split_at_mut(ciphertext_in_plaintext_out.len() - Self::tag_size()); + let in_tag = Tag::from_slice(in_tag); + self.0.decrypt_in_place_detached(nonce, &[], ciphertext, in_tag).is_ok() + } + } + } +} diff --git a/src/v1/aeadcipher/mod.rs b/src/v1/aeadcipher/mod.rs new file mode 100644 index 0000000..ec3d211 --- /dev/null +++ b/src/v1/aeadcipher/mod.rs @@ -0,0 +1,223 @@ +use crate::kind::{CipherCategory, CipherKind}; + +#[cfg(feature = "v1-aead-extra")] +mod aes_ccm; +mod aes_gcm; +#[cfg(feature = "v1-aead-extra")] +mod aes_gcm_siv; +mod chacha20_poly1305; +#[cfg(feature = "v1-aead-extra")] +mod sm4_ccm; +#[cfg(feature = "v1-aead-extra")] +mod sm4_gcm; +#[cfg(feature = "v1-aead-extra")] +mod sm4_gcm_cipher; +#[cfg(feature = "v1-aead-extra")] +mod xchacha20_poly1305; + +#[cfg(feature = "v1-aead-extra")] +pub use self::{ + aes_ccm::{Aes128Ccm, Aes256Ccm}, + aes_gcm_siv::{Aes128GcmSiv, Aes256GcmSiv}, + sm4_ccm::Sm4Ccm, + sm4_gcm::Sm4Gcm, + xchacha20_poly1305::XChaCha20Poly1305, +}; +pub use self::{ + aes_gcm::{Aes128Gcm, Aes256Gcm}, + chacha20_poly1305::ChaCha20Poly1305, +}; + +enum AeadCipherVariant { + Aes128Gcm(Aes128Gcm), + Aes256Gcm(Aes256Gcm), + ChaCha20Poly1305(ChaCha20Poly1305), + #[cfg(feature = "v1-aead-extra")] + XChaCha20Poly1305(XChaCha20Poly1305), + #[cfg(feature = "v1-aead-extra")] + Aes128GcmSiv(Aes128GcmSiv), + #[cfg(feature = "v1-aead-extra")] + Aes256GcmSiv(Aes256GcmSiv), + #[cfg(feature = "v1-aead-extra")] + Aes128Ccm(Aes128Ccm), + #[cfg(feature = "v1-aead-extra")] + Aes256Ccm(Aes256Ccm), + #[cfg(feature = "v1-aead-extra")] + Sm4Gcm(Sm4Gcm), + #[cfg(feature = "v1-aead-extra")] + Sm4Ccm(Sm4Ccm), +} + +impl AeadCipherVariant { + fn new(kind: CipherKind, key: &[u8]) -> AeadCipherVariant { + match kind { + CipherKind::AES_128_GCM => AeadCipherVariant::Aes128Gcm(Aes128Gcm::new(key)), + CipherKind::AES_256_GCM => AeadCipherVariant::Aes256Gcm(Aes256Gcm::new(key)), + CipherKind::CHACHA20_POLY1305 => AeadCipherVariant::ChaCha20Poly1305(ChaCha20Poly1305::new(key)), + #[cfg(feature = "v1-aead-extra")] + CipherKind::XCHACHA20_POLY1305 => AeadCipherVariant::XChaCha20Poly1305(XChaCha20Poly1305::new(key)), + #[cfg(feature = "v1-aead-extra")] + CipherKind::AES_128_GCM_SIV => AeadCipherVariant::Aes128GcmSiv(Aes128GcmSiv::new(key)), + #[cfg(feature = "v1-aead-extra")] + CipherKind::AES_256_GCM_SIV => AeadCipherVariant::Aes256GcmSiv(Aes256GcmSiv::new(key)), + #[cfg(feature = "v1-aead-extra")] + CipherKind::AES_128_CCM => AeadCipherVariant::Aes128Ccm(Aes128Ccm::new(key)), + #[cfg(feature = "v1-aead-extra")] + CipherKind::AES_256_CCM => AeadCipherVariant::Aes256Ccm(Aes256Ccm::new(key)), + #[cfg(feature = "v1-aead-extra")] + CipherKind::SM4_GCM => AeadCipherVariant::Sm4Gcm(Sm4Gcm::new(key)), + #[cfg(feature = "v1-aead-extra")] + CipherKind::SM4_CCM => AeadCipherVariant::Sm4Ccm(Sm4Ccm::new(key)), + _ => unreachable!("{:?} is not an AEAD cipher", kind), + } + } + + fn nonce_size(&self) -> usize { + match *self { + AeadCipherVariant::Aes128Gcm(..) => Aes128Gcm::nonce_size(), + AeadCipherVariant::Aes256Gcm(..) => Aes256Gcm::nonce_size(), + AeadCipherVariant::ChaCha20Poly1305(..) => ChaCha20Poly1305::nonce_size(), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::XChaCha20Poly1305(..) => XChaCha20Poly1305::nonce_size(), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Aes128GcmSiv(..) => Aes128GcmSiv::nonce_size(), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Aes256GcmSiv(..) => Aes256GcmSiv::nonce_size(), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Aes128Ccm(..) => Aes128Ccm::nonce_size(), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Aes256Ccm(..) => Aes256Ccm::nonce_size(), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Sm4Gcm(..) => Sm4Gcm::nonce_size(), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Sm4Ccm(..) => Sm4Ccm::nonce_size(), + } + } + + fn kind(&self) -> CipherKind { + match *self { + AeadCipherVariant::Aes128Gcm(..) => CipherKind::AES_128_GCM, + AeadCipherVariant::Aes256Gcm(..) => CipherKind::AES_256_GCM, + AeadCipherVariant::ChaCha20Poly1305(..) => CipherKind::CHACHA20_POLY1305, + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::XChaCha20Poly1305(..) => CipherKind::XCHACHA20_POLY1305, + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Aes128GcmSiv(..) => CipherKind::AES_128_GCM_SIV, + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Aes256GcmSiv(..) => CipherKind::AES_256_GCM_SIV, + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Aes128Ccm(..) => CipherKind::AES_128_CCM, + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Aes256Ccm(..) => CipherKind::AES_256_CCM, + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Sm4Gcm(..) => CipherKind::SM4_GCM, + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Sm4Ccm(..) => CipherKind::SM4_CCM, + } + } + + fn encrypt(&mut self, nonce: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + match *self { + AeadCipherVariant::Aes128Gcm(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), + AeadCipherVariant::Aes256Gcm(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), + AeadCipherVariant::ChaCha20Poly1305(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::XChaCha20Poly1305(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Aes128GcmSiv(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Aes256GcmSiv(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Aes128Ccm(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Aes256Ccm(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Sm4Gcm(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Sm4Ccm(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), + } + } + + fn decrypt(&mut self, nonce: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + match *self { + AeadCipherVariant::Aes128Gcm(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), + AeadCipherVariant::Aes256Gcm(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), + AeadCipherVariant::ChaCha20Poly1305(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::XChaCha20Poly1305(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Aes128GcmSiv(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Aes256GcmSiv(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Aes128Ccm(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Aes256Ccm(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Sm4Gcm(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), + #[cfg(feature = "v1-aead-extra")] + AeadCipherVariant::Sm4Ccm(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), + } + } +} + +pub struct AeadCipher { + cipher: AeadCipherVariant, + nlen: usize, + nonce: [u8; Self::N_MAX], +} + +impl AeadCipher { + const N_MAX: usize = 24; + + pub fn new(kind: CipherKind, key: &[u8]) -> Self { + let cipher = AeadCipherVariant::new(kind, key); + let nlen = cipher.nonce_size(); + debug_assert!(nlen <= Self::N_MAX); + let nonce = [0u8; Self::N_MAX]; + + Self { cipher, nlen, nonce } + } + + #[inline(always)] + pub fn kind(&self) -> CipherKind { + self.cipher.kind() + } + + #[inline(always)] + pub fn category(&self) -> CipherCategory { + CipherCategory::Aead + } + + #[inline(always)] + pub fn tag_len(&self) -> usize { + self.cipher.kind().tag_len() + } + + #[inline] + fn increase_nonce(&mut self) { + let mut c = self.nonce[0] as u16 + 1; + self.nonce[0] = c as u8; + c >>= 8; + let mut n = 1; + while n < self.nlen { + c += self.nonce[n] as u16; + self.nonce[n] = c as u8; + c >>= 8; + n += 1; + } + } + + pub fn encrypt(&mut self, plaintext_in_ciphertext_out: &mut [u8]) { + let nonce = &self.nonce[..self.nlen]; + self.cipher.encrypt(nonce, plaintext_in_ciphertext_out); + self.increase_nonce(); + } + + pub fn decrypt(&mut self, ciphertext_in_plaintext_out: &mut [u8]) -> bool { + let nonce = &self.nonce[..self.nlen]; + let ret = self.cipher.decrypt(nonce, ciphertext_in_plaintext_out); + self.increase_nonce(); + ret + } +} diff --git a/src/v1/aeadcipher/sm4_ccm.rs b/src/v1/aeadcipher/sm4_ccm.rs new file mode 100644 index 0000000..d4cffb3 --- /dev/null +++ b/src/v1/aeadcipher/sm4_ccm.rs @@ -0,0 +1,83 @@ +//! SM4_CCM +//! +//! https://datatracker.ietf.org/doc/html/rfc8998 + +use ccm::{ + aead::{generic_array::typenum::Unsigned, AeadCore, AeadInPlace, KeyInit, KeySizeUser}, + consts::{U12, U16}, + Ccm, + Nonce, + Tag, +}; +use sm4::Sm4; + +pub struct Sm4Ccm(Ccm); + +impl Sm4Ccm { + pub fn new(key: &[u8]) -> Sm4Ccm { + Sm4Ccm(Ccm::new_from_slice(key).expect("Sm4Ccm")) + } + + pub fn key_size() -> usize { + as KeySizeUser>::KeySize::to_usize() + } + + pub fn nonce_size() -> usize { + as AeadCore>::NonceSize::to_usize() + } + + pub fn tag_size() -> usize { + as AeadCore>::TagSize::to_usize() + } + + pub fn encrypt(&self, nonce: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + let nonce = Nonce::from_slice(nonce); + let (plaintext, out_tag) = + plaintext_in_ciphertext_out.split_at_mut(plaintext_in_ciphertext_out.len() - Self::tag_size()); + let tag = self + .0 + .encrypt_in_place_detached(nonce, &[], plaintext) + .expect("SM4_CCM encrypt"); + out_tag.copy_from_slice(tag.as_slice()) + } + + pub fn decrypt(&self, nonce: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + let nonce = Nonce::from_slice(nonce); + let (ciphertext, in_tag) = + ciphertext_in_plaintext_out.split_at_mut(ciphertext_in_plaintext_out.len() - Self::tag_size()); + let in_tag = Tag::from_slice(in_tag); + self.0.decrypt_in_place_detached(nonce, &[], ciphertext, in_tag).is_ok() + } +} + +#[cfg(test)] +mod test { + use ccm::{ + aead::{Aead, KeyInit, Payload}, + consts::{U12, U16}, + Ccm, + }; + use sm4::Sm4; + + #[test] + fn test_sm4_ccm() { + let iv = hex::decode("00001234567800000000ABCD").unwrap(); + let key = hex::decode("0123456789ABCDEFFEDCBA9876543210").unwrap(); + let plain_text = hex::decode("AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAA").unwrap(); + let aad = hex::decode("FEEDFACEDEADBEEFFEEDFACEDEADBEEFABADDAD2").unwrap(); + let mut cipher_text = hex::decode("48AF93501FA62ADBCD414CCE6034D895DDA1BF8F132F042098661572E7483094FD12E518CE062C98ACEE28D95DF4416BED31A2F04476C18BB40C84A74B97DC5B").unwrap(); + let mut tag = hex::decode("16842D4FA186F56AB33256971FA110F4").unwrap(); + cipher_text.append(&mut tag); // postfix tag + + let nonce = super::Nonce::from_slice(&iv); + + let cipher = Ccm::::new_from_slice(&key).unwrap(); + let plain_text_payload = Payload { + msg: &plain_text, + aad: &aad, + }; + let result = cipher.encrypt(nonce, plain_text_payload).unwrap(); + + assert_eq!(result, cipher_text); + } +} diff --git a/src/v1/aeadcipher/sm4_gcm.rs b/src/v1/aeadcipher/sm4_gcm.rs new file mode 100644 index 0000000..e11d1b1 --- /dev/null +++ b/src/v1/aeadcipher/sm4_gcm.rs @@ -0,0 +1,46 @@ +//! SM4-GCM + +use aead::{AeadCore, AeadInPlace, Key, KeyInit, KeySizeUser}; +use sm4::cipher::Unsigned; + +use super::sm4_gcm_cipher::{Nonce, Sm4Gcm as CryptoSm4Gcm, Tag}; + +pub struct Sm4Gcm(CryptoSm4Gcm); + +impl Sm4Gcm { + pub fn new(key: &[u8]) -> Sm4Gcm { + let key = Key::::from_slice(key); + Sm4Gcm(CryptoSm4Gcm::new(key)) + } + + pub fn key_size() -> usize { + ::KeySize::to_usize() + } + + pub fn nonce_size() -> usize { + ::NonceSize::to_usize() + } + + pub fn tag_size() -> usize { + ::TagSize::to_usize() + } + + pub fn encrypt(&self, nonce: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + let nonce = Nonce::from_slice(nonce); + let (plaintext, out_tag) = + plaintext_in_ciphertext_out.split_at_mut(plaintext_in_ciphertext_out.len() - Self::tag_size()); + let tag = self + .0 + .encrypt_in_place_detached(nonce, &[], plaintext) + .expect("SM4_GCM encrypt"); + out_tag.copy_from_slice(tag.as_slice()) + } + + pub fn decrypt(&self, nonce: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + let nonce = Nonce::from_slice(nonce); + let (ciphertext, in_tag) = + ciphertext_in_plaintext_out.split_at_mut(ciphertext_in_plaintext_out.len() - Self::tag_size()); + let in_tag = Tag::from_slice(in_tag); + self.0.decrypt_in_place_detached(nonce, &[], ciphertext, in_tag).is_ok() + } +} diff --git a/src/v1/aeadcipher/sm4_gcm_cipher.rs b/src/v1/aeadcipher/sm4_gcm_cipher.rs new file mode 100644 index 0000000..038e9d3 --- /dev/null +++ b/src/v1/aeadcipher/sm4_gcm_cipher.rs @@ -0,0 +1,202 @@ +//! SM4_GCM +//! +//! https://datatracker.ietf.org/doc/html/rfc8998 + +use aead::{ + consts::{U0, U12, U16}, + generic_array::GenericArray, + AeadCore, + AeadInPlace, + Key, + KeyInit, + KeySizeUser, +}; +use ghash::{universal_hash::UniversalHash, GHash}; +use sm4::{ + cipher::{BlockEncrypt, InnerIvInit, StreamCipherCore, Unsigned}, + Sm4, +}; + +/// Maximum length of associated data. +pub const A_MAX: u64 = 1 << 36; + +/// Maximum length of plaintext. +pub const P_MAX: u64 = 1 << 36; + +/// Maximum length of ciphertext. +pub const C_MAX: u64 = (1 << 36) + 16; + +/// SM4-GCM nonces. +pub type Nonce = GenericArray; + +/// SM4-GCM tags. +pub type Tag = GenericArray; + +/// SM4 block. +type Block = GenericArray; + +/// Counter mode with a 32-bit big endian counter. +type Ctr32BE<'a> = ctr::CtrCore<&'a Sm4, ctr::flavors::Ctr32BE>; + +pub struct Sm4Gcm { + cipher: Sm4, + ghash: GHash, +} + +impl KeySizeUser for Sm4Gcm { + type KeySize = ::KeySize; +} + +impl KeyInit for Sm4Gcm { + fn new(key: &Key) -> Self { + Sm4::new(key).into() + } +} + +impl From for Sm4Gcm { + fn from(cipher: Sm4) -> Self { + let mut ghash_key = ghash::Key::default(); + cipher.encrypt_block(&mut ghash_key); + + let ghash = GHash::new(&ghash_key); + + // ghash_key.zeroize(); + + Self { cipher, ghash } + } +} + +impl AeadCore for Sm4Gcm { + type CiphertextOverhead = U0; + type NonceSize = U12; + type TagSize = U16; +} + +impl AeadInPlace for Sm4Gcm { + fn encrypt_in_place_detached(&self, nonce: &Nonce, associated_data: &[u8], buffer: &mut [u8]) -> aead::Result { + if buffer.len() as u64 > P_MAX || associated_data.len() as u64 > A_MAX { + return Err(aead::Error); + } + + let (ctr, mask) = self.init_ctr(nonce); + + // TODO(tarcieri): interleave encryption with GHASH + // See: + ctr.apply_keystream_partial(buffer.into()); + + let full_tag = self.compute_tag(mask, associated_data, buffer); + Ok(Tag::clone_from_slice(&full_tag[..Self::TagSize::to_usize()])) + } + + fn decrypt_in_place_detached( + &self, + nonce: &Nonce, + associated_data: &[u8], + buffer: &mut [u8], + tag: &Tag, + ) -> aead::Result<()> { + if buffer.len() as u64 > C_MAX || associated_data.len() as u64 > A_MAX { + return Err(aead::Error); + } + + let (ctr, mask) = self.init_ctr(nonce); + + // TODO(tarcieri): interleave encryption with GHASH + // See: + let expected_tag = self.compute_tag(mask, associated_data, buffer); + + use subtle::ConstantTimeEq; + if expected_tag[..::TagSize::to_usize()] + .ct_eq(tag) + .into() + { + ctr.apply_keystream_partial(buffer.into()); + Ok(()) + } else { + Err(aead::Error) + } + } +} + +impl Sm4Gcm { + /// Initialize counter mode. + /// + /// See algorithm described in Section 7.2 of NIST SP800-38D: + /// + /// + /// > Define a block, J0, as follows: + /// > If len(IV)=96, then J0 = IV || 0{31} || 1. + /// > If len(IV) ≠ 96, then let s = 128 ⎡len(IV)/128⎤-len(IV), and + /// > J0=GHASH(IV||0s+64||[len(IV)]64). + fn init_ctr(&self, nonce: &Nonce) -> (Ctr32BE, Block) { + let j0 = if ::NonceSize::to_usize() == 12 { + let mut block = ghash::Block::default(); + block[..12].copy_from_slice(nonce); + block[15] = 1; + block + } else { + let mut ghash = self.ghash.clone(); + ghash.update_padded(nonce); + + let mut block = ghash::Block::default(); + let nonce_bits = (::NonceSize::to_usize() as u64) * 8; + block[8..].copy_from_slice(&nonce_bits.to_be_bytes()); + ghash.update(&[block]); + ghash.finalize() + }; + + let mut ctr = Ctr32BE::inner_iv_init(&self.cipher, &j0); + let mut tag_mask = Block::default(); + ctr.write_keystream_block(&mut tag_mask); + (ctr, tag_mask) + } + + /// Authenticate the given plaintext and associated data using GHASH. + fn compute_tag(&self, mask: Block, associated_data: &[u8], buffer: &[u8]) -> Tag { + let mut ghash = self.ghash.clone(); + ghash.update_padded(associated_data); + ghash.update_padded(buffer); + + let associated_data_bits = (associated_data.len() as u64) * 8; + let buffer_bits = (buffer.len() as u64) * 8; + + let mut block = ghash::Block::default(); + block[..8].copy_from_slice(&associated_data_bits.to_be_bytes()); + block[8..].copy_from_slice(&buffer_bits.to_be_bytes()); + ghash.update(&[block]); + + let mut tag = ghash.finalize(); + for (a, b) in tag.as_mut_slice().iter_mut().zip(mask.as_slice()) { + *a ^= *b; + } + + tag + } +} + +#[cfg(test)] +mod test { + use aead::{Aead, KeyInit, Payload}; + + #[test] + fn test_sm4_gcm() { + let iv = hex::decode("00001234567800000000ABCD").unwrap(); + let key = hex::decode("0123456789ABCDEFFEDCBA9876543210").unwrap(); + let plain_text = hex::decode("AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAA").unwrap(); + let aad = hex::decode("FEEDFACEDEADBEEFFEEDFACEDEADBEEFABADDAD2").unwrap(); + let mut cipher_text = hex::decode("17F399F08C67D5EE19D0DC9969C4BB7D5FD46FD3756489069157B282BB200735D82710CA5C22F0CCFA7CBF93D496AC15A56834CBCF98C397B4024A2691233B8D").unwrap(); + let mut tag = hex::decode("83DE3541E4C2B58177E065A9BF7B62EC").unwrap(); + cipher_text.append(&mut tag); // postfix tag + + let nonce = super::Nonce::from_slice(&iv); + + let cipher = super::Sm4Gcm::new_from_slice(&key).unwrap(); + let plain_text_payload = Payload { + msg: &plain_text, + aad: &aad, + }; + let result = cipher.encrypt(nonce, plain_text_payload).unwrap(); + + assert_eq!(result, cipher_text); + } +} diff --git a/src/v1/aeadcipher/xchacha20_poly1305.rs b/src/v1/aeadcipher/xchacha20_poly1305.rs new file mode 100644 index 0000000..aa77499 --- /dev/null +++ b/src/v1/aeadcipher/xchacha20_poly1305.rs @@ -0,0 +1,47 @@ +pub use chacha20poly1305::XChaCha20Poly1305 as CryptoXChaCha20Poly1305; +use chacha20poly1305::{ + aead::{generic_array::typenum::Unsigned, AeadCore, AeadInPlace, KeyInit, KeySizeUser}, + Key, + Tag, + XNonce, +}; + +pub struct XChaCha20Poly1305(CryptoXChaCha20Poly1305); + +impl XChaCha20Poly1305 { + pub fn new(key: &[u8]) -> XChaCha20Poly1305 { + let key = Key::from_slice(key); + XChaCha20Poly1305(CryptoXChaCha20Poly1305::new(key)) + } + + pub fn key_size() -> usize { + ::KeySize::to_usize() + } + + pub fn nonce_size() -> usize { + ::NonceSize::to_usize() + } + + pub fn tag_size() -> usize { + ::TagSize::to_usize() + } + + pub fn encrypt(&self, nonce: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + let nonce = XNonce::from_slice(nonce); + let (plaintext, out_tag) = + plaintext_in_ciphertext_out.split_at_mut(plaintext_in_ciphertext_out.len() - Self::tag_size()); + let tag = self + .0 + .encrypt_in_place_detached(nonce, &[], plaintext) + .expect("XCHACHA20_POLY1305 encrypt"); + out_tag.copy_from_slice(tag.as_slice()) + } + + pub fn decrypt(&self, nonce: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + let nonce = XNonce::from_slice(nonce); + let (ciphertext, in_tag) = + ciphertext_in_plaintext_out.split_at_mut(ciphertext_in_plaintext_out.len() - Self::tag_size()); + let in_tag = Tag::from_slice(in_tag); + self.0.decrypt_in_place_detached(nonce, &[], ciphertext, in_tag).is_ok() + } +} diff --git a/src/v1/cipher.rs b/src/v1/cipher.rs new file mode 100644 index 0000000..c9be091 --- /dev/null +++ b/src/v1/cipher.rs @@ -0,0 +1,231 @@ +use crate::kind::{CipherCategory, CipherKind}; + +#[cfg(feature = "v1-aead")] +use super::aeadcipher::AeadCipher; +use super::dummy::DummyCipher; +#[cfg(feature = "v1-stream")] +use super::streamcipher::StreamCipher; + +/// Generate random bytes into `iv_or_salt` +pub fn random_iv_or_salt(iv_or_salt: &mut [u8]) { + use rand::Rng; + + // Gen IV or Gen Salt by KEY-LEN + if iv_or_salt.is_empty() { + return; + } + + let mut rng = rand::thread_rng(); + loop { + rng.fill(iv_or_salt); + + // https://stackoverflow.com/questions/65367552/checking-a-vecu8-to-see-if-its-all-zero + let (prefix, aligned, suffix) = unsafe { iv_or_salt.align_to::() }; + let is_zeros = + prefix.iter().all(|&x| x == 0) && aligned.iter().all(|&x| x == 0) && suffix.iter().all(|&x| x == 0); + + if !is_zeros { + break; + } + } +} + +/// Key derivation of OpenSSL's [EVP_BytesToKey](https://wiki.openssl.org/index.php/Manual:EVP_BytesToKey(3)) +pub fn openssl_bytes_to_key(password: &[u8], key: &mut [u8]) { + use md5::{Digest, Md5}; + + let key_len = key.len(); + + let mut last_digest = None; + + let mut offset = 0usize; + while offset < key_len { + let mut m = Md5::new(); + if let Some(digest) = last_digest { + m.update(&digest); + } + + m.update(password); + + let digest = m.finalize(); + + let amt = std::cmp::min(key_len - offset, digest.len()); + key[offset..offset + amt].copy_from_slice(&digest[..amt]); + + offset += amt; + last_digest = Some(digest); + } +} + +/// Unified interface of Ciphers +#[allow(clippy::large_enum_variant)] +pub enum Cipher { + Dummy(DummyCipher), + #[cfg(feature = "v1-stream")] + Stream(StreamCipher), + #[cfg(feature = "v1-aead")] + Aead(AeadCipher), +} + +macro_rules! cipher_method_forward { + (ref $self:expr, $method:ident $(, $param:expr),*) => { + match *$self { + Cipher::Dummy(ref c) => c.$method($($param),*), + #[cfg(feature = "v1-stream")] + Cipher::Stream(ref c) => c.$method($($param),*), + #[cfg(feature = "v1-aead")] + Cipher::Aead(ref c) => c.$method($($param),*), + } + }; + + (mut $self:expr, $method:ident $(, $param:expr),*) => { + match *$self { + Cipher::Dummy(ref mut c) => c.$method($($param),*), + #[cfg(feature = "v1-stream")] + Cipher::Stream(ref mut c) => c.$method($($param),*), + #[cfg(feature = "v1-aead")] + Cipher::Aead(ref mut c) => c.$method($($param),*), + } + }; +} + +impl Cipher { + /// Create a new Cipher of `kind` + /// + /// - Stream Ciphers initialize with IV + /// - AEAD Ciphers initialize with SALT + pub fn new(kind: CipherKind, key: &[u8], iv_or_salt: &[u8]) -> Cipher { + let category = kind.category(); + + match category { + CipherCategory::None => { + let _ = key; + let _ = iv_or_salt; + + Cipher::Dummy(DummyCipher::new()) + } + #[cfg(feature = "v1-stream")] + CipherCategory::Stream => Cipher::Stream(StreamCipher::new(kind, key, iv_or_salt)), + #[cfg(feature = "v1-aead")] + CipherCategory::Aead => { + use cfg_if::cfg_if; + + const SUBKEY_INFO: &'static [u8] = b"ss-subkey"; + const MAX_KEY_LEN: usize = 64; + + let ikm = key; + let mut okm = [0u8; MAX_KEY_LEN]; + + cfg_if! { + if #[cfg(feature = "ring")] { + use ring_compat::ring::hkdf::{Salt, HKDF_SHA1_FOR_LEGACY_USE_ONLY, KeyType}; + + struct CryptoKeyType(usize); + + impl KeyType for CryptoKeyType { + #[inline] + fn len(&self) -> usize { + self.0 + } + } + + let salt = Salt::new(HKDF_SHA1_FOR_LEGACY_USE_ONLY, iv_or_salt); + let prk = salt.extract(ikm); + let rokm = prk + .expand(&[SUBKEY_INFO], CryptoKeyType(ikm.len())) + .expect("HKDF-SHA1-EXPAND"); + + rokm.fill(&mut okm[..ikm.len()]).expect("HKDF-SHA1-FILL"); + } else { + use hkdf::Hkdf; + use sha1::Sha1; + + let hk = Hkdf::::new(Some(iv_or_salt), ikm); + hk.expand(SUBKEY_INFO, &mut okm).expect("HKDF-SHA1"); + } + } + + let subkey = &okm[..ikm.len()]; + Cipher::Aead(AeadCipher::new(kind, subkey)) + } + #[allow(unreachable_patterns)] + _ => unimplemented!("Category {:?} is not v1 protocol", category), + } + } + + /// Get the `CipherCategory` of the current cipher + pub fn category(&self) -> CipherCategory { + cipher_method_forward!(ref self, category) + } + + /// Get the `CipherKind` of the current cipher + pub fn kind(&self) -> CipherKind { + cipher_method_forward!(ref self, kind) + } + + /// Get the TAG length of AEAD ciphers + pub fn tag_len(&self) -> usize { + cipher_method_forward!(ref self, tag_len) + } + + /// Encrypt a packet. Encrypted result will be written in `pkt` + /// + /// - Stream Ciphers: the size of input and output packets are the same + /// - AEAD Ciphers: the size of output must be at least `input.len() + TAG_LEN` + pub fn encrypt_packet(&mut self, pkt: &mut [u8]) { + cipher_method_forward!(mut self, encrypt, pkt) + } + + /// Decrypt a packet. Decrypted result will be written in `pkt` + /// + /// - Stream Ciphers: the size of input and output packets are the same + /// - AEAD Ciphers: the size of output is `input.len() - TAG_LEN` + #[must_use] + pub fn decrypt_packet(&mut self, pkt: &mut [u8]) -> bool { + cipher_method_forward!(mut self, decrypt, pkt) + } +} + +#[test] +fn test_cipher_new_none() { + let key = [2u8; 16]; + let salt = [1u8; 16]; + let kind = CipherKind::NONE; + + let cipher = Cipher::new(kind, &key, &salt); + assert_eq!(cipher.tag_len(), 0); +} + +#[cfg(feature = "v1-aead")] +#[test] +fn test_cipher_new_aead() { + let key = [2u8; 16]; + let salt = [1u8; 16]; + let kind = CipherKind::AES_128_GCM; + + let cipher = Cipher::new(kind, &key, &salt); + assert_eq!(cipher.tag_len(), 16); +} + +#[cfg(feature = "v1-stream")] +#[test] +fn test_cipher_new_stream() { + let key = [2u8; 32]; + let iv = [1u8; 12]; + let kind = CipherKind::CHACHA20; + + let cipher = Cipher::new(kind, &key, &iv); + assert_eq!(cipher.tag_len(), 0); +} + +#[test] +fn test_send() { + fn test() {} + test::(); +} + +#[test] +fn test_sync() { + fn test() {} + test::(); +} diff --git a/src/v1/dummy.rs b/src/v1/dummy.rs new file mode 100644 index 0000000..48bf901 --- /dev/null +++ b/src/v1/dummy.rs @@ -0,0 +1,41 @@ +use crate::kind::{CipherCategory, CipherKind}; + +/// Dummy cipher +#[derive(Clone)] +pub struct DummyCipher; + +impl core::fmt::Debug for DummyCipher { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("DummyCipher").finish() + } +} + +impl DummyCipher { + pub fn new() -> Self { + Self + } + + pub fn kind(&self) -> CipherKind { + CipherKind::NONE + } + + pub fn category(&self) -> CipherCategory { + CipherCategory::None + } + + pub fn tag_len(&self) -> usize { + 0 + } + + pub fn encrypt(&mut self, _plaintext_in_ciphertext_out: &mut [u8]) {} + + pub fn decrypt(&mut self, _ciphertext_in_plaintext_out: &mut [u8]) -> bool { + true + } +} + +impl Default for DummyCipher { + fn default() -> Self { + Self::new() + } +} diff --git a/src/v1/mod.rs b/src/v1/mod.rs new file mode 100644 index 0000000..6472a63 --- /dev/null +++ b/src/v1/mod.rs @@ -0,0 +1,11 @@ +//! Shadowsocks V1 protocol ciphers + +#[cfg(feature = "v1-aead")] +pub(crate) mod aeadcipher; +pub(crate) mod dummy; +#[cfg(feature = "v1-stream")] +pub(crate) mod streamcipher; + +mod cipher; + +pub use self::cipher::*; diff --git a/src/v1/streamcipher/cfb.rs b/src/v1/streamcipher/cfb.rs new file mode 100644 index 0000000..2ecd0c4 --- /dev/null +++ b/src/v1/streamcipher/cfb.rs @@ -0,0 +1,426 @@ +// 6.3 The Cipher Feedback Mode, (Page-18) +// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf +use super::crypto::{ + aes::{Aes128, Aes192, Aes256}, + camellia::{Camellia128, Camellia192, Camellia256}, +}; + +#[derive(Debug, Clone, Copy)] +struct Bits(pub u8); + +impl Bits { + pub fn bit(&self, pos: usize) -> bool { + assert!(pos < 8); + let pos = 8 - pos - 1; + self.0 & 1 << pos != 0 + } + + pub fn set_bit(&mut self, pos: usize, val: bool) { + assert!(pos < 8); + let pos = 8 - pos - 1; + self.0 ^= (0u8.wrapping_sub(val as u8) ^ self.0) & 1 << pos; + } + + pub fn bit_xor(&mut self, pos: usize, other: u8) { + let a = self.bit(pos); + let b = Bits(other).bit(0); + if a != b { + self.set_bit(pos, true); + } else { + self.set_bit(pos, false); + } + } +} + +fn left_shift_1(bytes: &mut [u8], bit: bool) { + let mut last_bit = if bit { 0b0000_0001 } else { 0b0000_0000 }; + for byte in bytes.iter_mut().rev() { + let b = (*byte & 0b1000_0000) >> 7; + *byte <<= 1; + *byte |= last_bit; + last_bit = b; + } +} + +macro_rules! impl_block_cipher_with_cfb1_mode { + ($name:tt, $cipher:tt) => { + #[derive(Clone)] + pub struct $name { + cipher: $cipher, + last_input_block: [u8; Self::BLOCK_LEN], + } + + impl $name { + pub const B: usize = Self::BLOCK_LEN * 8; + pub const BLOCK_LEN: usize = $cipher::BLOCK_LEN; + pub const IV_LEN: usize = $cipher::BLOCK_LEN; + pub const KEY_LEN: usize = $cipher::KEY_LEN; + // The block size, in bits. + pub const S: usize = 1; + + // The number of bits in a data segment. + + pub fn new(key: &[u8], iv: &[u8]) -> Self { + assert!(Self::BLOCK_LEN <= 16); + assert!(Self::S <= Self::B); + assert_eq!(key.len(), Self::KEY_LEN); + assert_eq!(iv.len(), Self::IV_LEN); + + let cipher = $cipher::new(key); + + let mut last_input_block = [0u8; Self::IV_LEN]; + last_input_block.copy_from_slice(iv); + + Self { + cipher, + last_input_block, + } + } + + pub fn encryptor_update(&mut self, plaintext_in_ciphertext_out: &mut [u8]) { + #[allow(unused_assignments)] + let mut last_segment = false; + + for i in 0..plaintext_in_ciphertext_out.len() { + for bit_pos in 0..8 { + let mut keystream = self.last_input_block.clone(); + self.cipher.encrypt(&mut keystream); + + let mut byte_bits = Bits(plaintext_in_ciphertext_out[i]); + byte_bits.bit_xor(bit_pos, keystream[0]); + last_segment = byte_bits.bit(bit_pos); + plaintext_in_ciphertext_out[i] = byte_bits.0; + + // left shift 1 bits + left_shift_1(&mut self.last_input_block, last_segment); + } + } + } + + pub fn decryptor_update(&mut self, ciphertext_in_plaintext_out: &mut [u8]) { + #[allow(unused_assignments)] + let mut last_segment = false; + + for i in 0..ciphertext_in_plaintext_out.len() { + for bit_pos in 0..8 { + let mut keystream = self.last_input_block.clone(); + self.cipher.encrypt(&mut keystream); + + let mut byte_bits = Bits(ciphertext_in_plaintext_out[i]); + last_segment = byte_bits.bit(bit_pos); + byte_bits.bit_xor(bit_pos, keystream[0]); + ciphertext_in_plaintext_out[i] = byte_bits.0; + + // left shift 1 bits + left_shift_1(&mut self.last_input_block, last_segment); + } + } + } + } + }; +} + +macro_rules! impl_block_cipher_with_cfb8_mode { + ($name:tt, $cipher:tt) => { + #[derive(Clone)] + pub struct $name { + cipher: $cipher, + last_input_block: [u8; Self::BLOCK_LEN], + } + + impl $name { + pub const B: usize = Self::BLOCK_LEN * 8; + pub const BLOCK_LEN: usize = $cipher::BLOCK_LEN; + pub const IV_LEN: usize = $cipher::BLOCK_LEN; + pub const KEY_LEN: usize = $cipher::KEY_LEN; + // The block size, in bits. + pub const S: usize = 8; + + // The number of bits in a data segment. + + pub fn new(key: &[u8], iv: &[u8]) -> Self { + assert!(Self::S <= Self::B); + assert_eq!(key.len(), Self::KEY_LEN); + assert_eq!(iv.len(), Self::IV_LEN); + + let cipher = $cipher::new(key); + + let mut last_input_block = [0u8; Self::IV_LEN]; + last_input_block.copy_from_slice(iv); + + Self { + cipher, + last_input_block, + } + } + + pub fn encryptor_update(&mut self, plaintext_in_ciphertext_out: &mut [u8]) { + #[allow(unused_assignments)] + let mut last_segment = 0u8; + + for i in 0..plaintext_in_ciphertext_out.len() { + let mut keystream = self.last_input_block.clone(); + self.cipher.encrypt(&mut keystream); + + plaintext_in_ciphertext_out[i] ^= keystream[0]; + last_segment = plaintext_in_ciphertext_out[i]; + + // left shift 8 bits + let mut tmp = [0u8; Self::BLOCK_LEN]; + tmp[0..Self::BLOCK_LEN - 1].copy_from_slice(&self.last_input_block[1..]); + tmp[Self::BLOCK_LEN - 1] = last_segment; + self.last_input_block = tmp; + } + } + + pub fn decryptor_update(&mut self, ciphertext_in_plaintext_out: &mut [u8]) { + #[allow(unused_assignments)] + let mut last_segment = 0u8; + + for i in 0..ciphertext_in_plaintext_out.len() { + let mut keystream = self.last_input_block.clone(); + self.cipher.encrypt(&mut keystream); + + last_segment = ciphertext_in_plaintext_out[i]; + ciphertext_in_plaintext_out[i] ^= keystream[0]; + + // left shift 8 bits + let mut tmp = [0u8; Self::BLOCK_LEN]; + tmp[0..Self::BLOCK_LEN - 1].copy_from_slice(&self.last_input_block[1..]); + tmp[Self::BLOCK_LEN - 1] = last_segment; + self.last_input_block = tmp; + } + } + } + }; +} + +macro_rules! impl_block_cipher_with_cfb128_mode { + ($name:tt, $cipher:tt) => { + #[derive(Clone)] + pub struct $name { + cipher: $cipher, + last_input_block: [u8; Self::BLOCK_LEN], + keystream: [u8; Self::BLOCK_LEN], + offset: usize, + } + + impl $name { + pub const B: usize = Self::BLOCK_LEN * 8; + pub const BLOCK_LEN: usize = $cipher::BLOCK_LEN; + pub const IV_LEN: usize = $cipher::BLOCK_LEN; + pub const KEY_LEN: usize = $cipher::KEY_LEN; + // The block size, in bits. + pub const S: usize = 128; + + // The number of bits in a data segment. + + pub fn new(key: &[u8], iv: &[u8]) -> Self { + assert!(Self::S <= Self::B); + assert_eq!(key.len(), Self::KEY_LEN); + assert_eq!(iv.len(), Self::IV_LEN); + + let cipher = $cipher::new(key); + + let mut last_input_block = [0u8; Self::IV_LEN]; + last_input_block.copy_from_slice(iv); + + let mut keystream = last_input_block.clone(); + cipher.encrypt(&mut keystream); + + Self { + cipher, + last_input_block, + keystream, + offset: 0usize, + } + } + + pub fn encryptor_update(&mut self, plaintext_in_ciphertext_out: &mut [u8]) { + for i in 0..plaintext_in_ciphertext_out.len() { + if self.offset == Self::BLOCK_LEN { + self.keystream = self.last_input_block.clone(); + self.cipher.encrypt(&mut self.keystream); + + self.offset = 0; + } + + plaintext_in_ciphertext_out[i] ^= self.keystream[self.offset]; + self.last_input_block[self.offset] = plaintext_in_ciphertext_out[i]; + + self.offset += 1; + } + } + + pub fn decryptor_update(&mut self, ciphertext_in_plaintext_out: &mut [u8]) { + for i in 0..ciphertext_in_plaintext_out.len() { + if self.offset == Self::BLOCK_LEN { + self.keystream = self.last_input_block.clone(); + self.cipher.encrypt(&mut self.keystream); + + self.offset = 0; + } + + self.last_input_block[self.offset] = ciphertext_in_plaintext_out[i]; + ciphertext_in_plaintext_out[i] ^= self.keystream[self.offset]; + + self.offset += 1; + } + } + } + }; +} + +impl_block_cipher_with_cfb1_mode!(Aes128Cfb1, Aes128); +impl_block_cipher_with_cfb1_mode!(Aes192Cfb1, Aes192); +impl_block_cipher_with_cfb1_mode!(Aes256Cfb1, Aes256); +impl_block_cipher_with_cfb1_mode!(Camellia128Cfb1, Camellia128); +impl_block_cipher_with_cfb1_mode!(Camellia192Cfb1, Camellia192); +impl_block_cipher_with_cfb1_mode!(Camellia256Cfb1, Camellia256); + +impl_block_cipher_with_cfb8_mode!(Aes128Cfb8, Aes128); +impl_block_cipher_with_cfb8_mode!(Aes192Cfb8, Aes192); +impl_block_cipher_with_cfb8_mode!(Aes256Cfb8, Aes256); +impl_block_cipher_with_cfb8_mode!(Camellia128Cfb8, Camellia128); +impl_block_cipher_with_cfb8_mode!(Camellia192Cfb8, Camellia192); +impl_block_cipher_with_cfb8_mode!(Camellia256Cfb8, Camellia256); + +impl_block_cipher_with_cfb128_mode!(Aes128Cfb128, Aes128); +impl_block_cipher_with_cfb128_mode!(Aes192Cfb128, Aes192); +impl_block_cipher_with_cfb128_mode!(Aes256Cfb128, Aes256); +impl_block_cipher_with_cfb128_mode!(Camellia128Cfb128, Camellia128); +impl_block_cipher_with_cfb128_mode!(Camellia192Cfb128, Camellia192); +impl_block_cipher_with_cfb128_mode!(Camellia256Cfb128, Camellia256); + +#[test] +fn test_aes128_cfb8() { + let key = hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(); + let iv = hex::decode("000102030405060708090a0b0c0d0e0f").unwrap(); + let plaintext = hex::decode( + "\ +6bc1bee22e409f96e93d7e117393172a\ +ae2d8a", + ) + .unwrap(); + + let mut cipher = Aes128Cfb8::new(&key, &iv); + let mut ciphertext = plaintext.clone(); + cipher.encryptor_update(&mut ciphertext); + + let mut cipher = Aes128Cfb8::new(&key, &iv); + let mut cleartext = ciphertext.clone(); + cipher.decryptor_update(&mut cleartext); + + assert_eq!(&cleartext[..], &plaintext[..]); +} + +#[test] +fn test_aes128_cfb1_enc() { + // F.3.1 CFB1-AES128.Encrypt, (Page-36) + // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + let key = hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(); + let iv = hex::decode("000102030405060708090a0b0c0d0e0f").unwrap(); + + let mut cipher = Aes128Cfb1::new(&key, &iv); + // 0110_1011_1100_0001 + // 0110_1000_1011_0011 + let plaintext = [0x6b, 0xc1]; + let mut ciphertext = plaintext.clone(); + cipher.encryptor_update(&mut ciphertext); + assert_eq!(&ciphertext[..], &[0x68, 0xb3]); +} + +#[test] +fn test_aes128_cfb1_dec() { + // F.3.2 CFB1-AES128.Decrypt, (Page-37) + // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + let key = hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(); + let iv = hex::decode("000102030405060708090a0b0c0d0e0f").unwrap(); + + let mut cipher = Aes128Cfb1::new(&key, &iv); + + let ciphertext = [0x68, 0xb3]; + let mut plaintext = ciphertext.clone(); + cipher.decryptor_update(&mut plaintext); + assert_eq!(&plaintext[..], &[0x6b, 0xc1]); +} + +#[test] +fn test_aes128_cfb8_enc() { + // F.3.7 CFB8-AES128.Encrypt, (Page-46) + // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + let key = hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(); + let iv = hex::decode("000102030405060708090a0b0c0d0e0f").unwrap(); + + let mut cipher = Aes128Cfb8::new(&key, &iv); + + let plaintext = [0x6b, 0xc1, 0xbe, 0xe2, 0x2e]; + let mut ciphertext = plaintext.clone(); + cipher.encryptor_update(&mut ciphertext); + assert_eq!(&ciphertext[..], &[0x3b, 0x79, 0x42, 0x4c, 0x9c,]); +} + +#[test] +fn test_aes128_cfb8_dec() { + // F.3.7 CFB8-AES128.Decrypt, (Page-48) + // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + let key = hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(); + let iv = hex::decode("000102030405060708090a0b0c0d0e0f").unwrap(); + + let mut cipher = Aes128Cfb8::new(&key, &iv); + + let ciphertext = [0x3b, 0x79, 0x42, 0x4c, 0x9c]; + let mut plaintext = ciphertext.clone(); + cipher.decryptor_update(&mut plaintext); + assert_eq!(&plaintext[..], &[0x6b, 0xc1, 0xbe, 0xe2, 0x2e]); +} + +#[test] +fn test_aes128_cfb128_enc() { + // F.3.13 CFB128-AES128.Encrypt, (Page-57) + // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + let key = hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(); + let iv = hex::decode("000102030405060708090a0b0c0d0e0f").unwrap(); + + let mut cipher = Aes128Cfb128::new(&key, &iv); + + let plaintext = hex::decode( + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f24", + ) + .unwrap(); + + let mut ciphertext = plaintext.clone(); + cipher.encryptor_update(&mut ciphertext); + assert_eq!( + &ciphertext[..], + &hex::decode( + "3b3fd92eb72dad20333449f8e83cfb4ac8a64537a0b3a93fcde3cdad9f1ce58b26751f67a3cbb140b1808cf187a4f4dfc04b05" + ) + .unwrap()[..] + ); +} + +#[test] +fn test_aes128_cfb128_dec() { + // F.3.14 CFB128-AES128.Decrypt, (Page-57) + // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + let key = hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(); + let iv = hex::decode("000102030405060708090a0b0c0d0e0f").unwrap(); + + let mut cipher = Aes128Cfb128::new(&key, &iv); + + let ciphertext = hex::decode( + "3b3fd92eb72dad20333449f8e83cfb4ac8a64537a0b3a93fcde3cdad9f1ce58b26751f67a3cbb140b1808cf187a4f4dfc04b05", + ) + .unwrap(); + + let mut plaintext = ciphertext.clone(); + cipher.decryptor_update(&mut plaintext); + assert_eq!( + &plaintext[..], + &hex::decode( + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f24" + ) + .unwrap()[..] + ); +} diff --git a/src/v1/streamcipher/chacha20.rs b/src/v1/streamcipher/chacha20.rs new file mode 100644 index 0000000..b30386c --- /dev/null +++ b/src/v1/streamcipher/chacha20.rs @@ -0,0 +1,39 @@ +use chacha20::{ + cipher::{IvSizeUser, KeyIvInit, KeySizeUser, StreamCipher, Unsigned}, + ChaCha20, + Key, + Nonce, +}; + +/// ChaCha20 for IETF Protocols +/// +/// https://tools.ietf.org/html/rfc8439 +pub struct Chacha20 { + cipher: ChaCha20, +} + +impl Chacha20 { + pub fn new(key: &[u8], nonce: &[u8]) -> Self { + let key = Key::from_slice(key); + let nonce = Nonce::from_slice(nonce); + let cipher = ChaCha20::new(key, nonce); + + Self { cipher } + } + + pub fn encryptor_update(&mut self, plaintext_in_ciphertext_out: &mut [u8]) { + self.cipher.apply_keystream(plaintext_in_ciphertext_out); + } + + pub fn decryptor_update(&mut self, ciphertext_in_plaintext_out: &mut [u8]) { + self.cipher.apply_keystream(ciphertext_in_plaintext_out); + } + + pub fn key_size() -> usize { + ::KeySize::to_usize() + } + + pub fn nonce_size() -> usize { + ::IvSize::to_usize() + } +} diff --git a/src/v1/streamcipher/crypto/aes.rs b/src/v1/streamcipher/crypto/aes.rs new file mode 100644 index 0000000..ed11342 --- /dev/null +++ b/src/v1/streamcipher/crypto/aes.rs @@ -0,0 +1,75 @@ +#![allow(dead_code)] + +use aes::{ + cipher::{BlockDecrypt, BlockEncrypt, BlockSizeUser, KeyInit, Unsigned}, + Aes128 as CryptoAes128, + Aes192 as CryptoAes192, + Aes256 as CryptoAes256, + Block, +}; + +#[derive(Debug, Clone)] +pub struct Aes128(CryptoAes128); + +impl Aes128 { + pub const BLOCK_LEN: usize = ::BlockSize::USIZE; + pub const KEY_LEN: usize = 16; + + pub fn new(key: &[u8]) -> Aes128 { + Aes128(CryptoAes128::new_from_slice(key).expect("Aes128")) + } + + pub fn encrypt(&self, block: &mut [u8]) { + let block = Block::from_mut_slice(block); + self.0.encrypt_block(block); + } + + pub fn decrypt(&self, block: &mut [u8]) { + let block = Block::from_mut_slice(block); + self.0.decrypt_block(block); + } +} + +#[derive(Debug, Clone)] +pub struct Aes192(CryptoAes192); + +impl Aes192 { + pub const BLOCK_LEN: usize = ::BlockSize::USIZE; + pub const KEY_LEN: usize = 24; + + pub fn new(key: &[u8]) -> Aes192 { + Aes192(CryptoAes192::new_from_slice(key).expect("Aes192")) + } + + pub fn encrypt(&self, block: &mut [u8]) { + let block = Block::from_mut_slice(block); + self.0.encrypt_block(block); + } + + pub fn decrypt(&self, block: &mut [u8]) { + let block = Block::from_mut_slice(block); + self.0.decrypt_block(block); + } +} + +#[derive(Debug, Clone)] +pub struct Aes256(CryptoAes256); + +impl Aes256 { + pub const BLOCK_LEN: usize = ::BlockSize::USIZE; + pub const KEY_LEN: usize = 32; + + pub fn new(key: &[u8]) -> Aes256 { + Aes256(CryptoAes256::new_from_slice(key).expect("Aes256")) + } + + pub fn encrypt(&self, block: &mut [u8]) { + let block = Block::from_mut_slice(block); + self.0.encrypt_block(block); + } + + pub fn decrypt(&self, block: &mut [u8]) { + let block = Block::from_mut_slice(block); + self.0.decrypt_block(block); + } +} diff --git a/src/v1/streamcipher/crypto/camellia.rs b/src/v1/streamcipher/crypto/camellia.rs new file mode 100644 index 0000000..38e929c --- /dev/null +++ b/src/v1/streamcipher/crypto/camellia.rs @@ -0,0 +1,74 @@ +#![allow(dead_code)] + +use camellia::{ + cipher::{Block, BlockDecrypt, BlockEncrypt, BlockSizeUser, KeyInit, KeySizeUser, Unsigned}, + Camellia128 as CryptoCamellia128, + Camellia192 as CryptoCamellia192, + Camellia256 as CryptoCamellia256, +}; + +#[derive(Debug, Clone)] +pub struct Camellia128(CryptoCamellia128); + +impl Camellia128 { + pub const BLOCK_LEN: usize = ::BlockSize::USIZE; + pub const KEY_LEN: usize = ::KeySize::USIZE; + + pub fn new(key: &[u8]) -> Camellia128 { + Camellia128(CryptoCamellia128::new_from_slice(key).expect("Camellia128")) + } + + pub fn encrypt(&self, block: &mut [u8]) { + let block = Block::::from_mut_slice(block); + self.0.encrypt_block(block); + } + + pub fn decrypt(&self, block: &mut [u8]) { + let block = Block::::from_mut_slice(block); + self.0.decrypt_block(block); + } +} + +#[derive(Debug, Clone)] +pub struct Camellia192(CryptoCamellia192); + +impl Camellia192 { + pub const BLOCK_LEN: usize = ::BlockSize::USIZE; + pub const KEY_LEN: usize = ::KeySize::USIZE; + + pub fn new(key: &[u8]) -> Camellia192 { + Camellia192(CryptoCamellia192::new_from_slice(key).expect("Camellia192")) + } + + pub fn encrypt(&self, block: &mut [u8]) { + let block = Block::::from_mut_slice(block); + self.0.encrypt_block(block); + } + + pub fn decrypt(&self, block: &mut [u8]) { + let block = Block::::from_mut_slice(block); + self.0.decrypt_block(block); + } +} + +#[derive(Debug, Clone)] +pub struct Camellia256(CryptoCamellia256); + +impl Camellia256 { + pub const BLOCK_LEN: usize = ::BlockSize::USIZE; + pub const KEY_LEN: usize = ::KeySize::USIZE; + + pub fn new(key: &[u8]) -> Camellia256 { + Camellia256(CryptoCamellia256::new_from_slice(key).expect("Camellia256")) + } + + pub fn encrypt(&self, block: &mut [u8]) { + let block = Block::::from_mut_slice(block); + self.0.encrypt_block(block); + } + + pub fn decrypt(&self, block: &mut [u8]) { + let block = Block::::from_mut_slice(block); + self.0.decrypt_block(block); + } +} diff --git a/src/v1/streamcipher/crypto/mod.rs b/src/v1/streamcipher/crypto/mod.rs new file mode 100644 index 0000000..a0108d3 --- /dev/null +++ b/src/v1/streamcipher/crypto/mod.rs @@ -0,0 +1,3 @@ +pub mod aes; +pub mod camellia; +pub mod rc4; diff --git a/src/v1/streamcipher/crypto/rc4.rs b/src/v1/streamcipher/crypto/rc4.rs new file mode 100644 index 0000000..9ede49f --- /dev/null +++ b/src/v1/streamcipher/crypto/rc4.rs @@ -0,0 +1,119 @@ +// RC4 Source Code +// http://cypherpunks.venona.com/archive/1994/09/msg00304.html +// +// https://en.wikipedia.org/wiki/RC4 +const INIT_STATE: [u8; 256] = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, + 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, + 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, + 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, + 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, + 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, + 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, + 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, + 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, +]; + +/// RC4 (Rivest Cipher 4 also known as ARC4 or ARCFOUR) +#[derive(Clone)] +pub struct Rc4 { + x: u8, + y: u8, + state: [u8; 256], +} + +impl core::fmt::Debug for Rc4 { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("Rc4").finish() + } +} + +impl Rc4 { + // In bytes + pub const MAX_KEY_LEN: usize = 256; + pub const MIN_KEY_LEN: usize = 1; + + // In bytes + + pub fn new(key: &[u8]) -> Self { + assert!(key.len() >= Self::MIN_KEY_LEN && key.len() <= Self::MAX_KEY_LEN); + + let key_len = key.len() as u8; + let mut state = INIT_STATE; + + let mut index1 = 0u8; + let mut index2 = 0u8; + for counter in 0..256 { + index2 = key[index1 as usize].wrapping_add(state[counter]).wrapping_add(index2); + state.swap(counter as usize, index2 as usize); + index1 = index1.wrapping_add(1) % key_len; + } + + Self { x: 0, y: 0, state } + } + + #[inline] + fn in_place(&mut self, data: &mut [u8]) { + #![allow(unused_assignments)] + + let mut xor_index = 0u8; + + for counter in 0..data.len() { + self.x = self.x.wrapping_add(1); + self.y = self.y.wrapping_add(self.state[self.x as usize]); + + self.state.swap(self.x as usize, self.y as usize); + + let a = self.state[self.x as usize]; + let b = self.state[self.y as usize]; + xor_index = a.wrapping_add(b); + + data[counter] ^= self.state[xor_index as usize]; + } + } + + pub fn encrypt_slice(&mut self, plaintext_and_ciphertext: &mut [u8]) { + self.in_place(plaintext_and_ciphertext); + } + + pub fn decrypt_slice(&mut self, ciphertext_and_plaintext: &mut [u8]) { + self.in_place(ciphertext_and_plaintext); + } +} + +#[test] +fn test_rc4() { + // Test vectors + // https://en.wikipedia.org/wiki/RC4#Test_vectors + let key: &[u8] = b"Key"; + let mut rc4 = Rc4::new(&key); + let plaintext = b"Plaintext"; + let mut ciphertext = plaintext.clone(); + rc4.encrypt_slice(&mut ciphertext); + assert_eq!(&ciphertext[..], &hex::decode("BBF316E8D940AF0AD3").unwrap()[..]); + + let key: &[u8] = b"Wiki"; + let mut rc4 = Rc4::new(&key); + let plaintext = b"pedia"; + let mut ciphertext = plaintext.clone(); + rc4.encrypt_slice(&mut ciphertext); + assert_eq!(&ciphertext[..], &hex::decode("1021BF0420").unwrap()[..]); + + let key: &[u8] = b"Secret"; + let mut rc4 = Rc4::new(&key); + let plaintext = b"Attack at dawn"; + let mut ciphertext = plaintext.clone(); + rc4.encrypt_slice(&mut ciphertext); + assert_eq!( + &ciphertext[..], + &hex::decode("45A01F645FC35B383552544B9BF5").unwrap()[..] + ); + + // 2. Test Vectors for RC4 + // https://tools.ietf.org/html/rfc6229#section-2 +} diff --git a/src/v1/streamcipher/ctr.rs b/src/v1/streamcipher/ctr.rs new file mode 100644 index 0000000..37650e2 --- /dev/null +++ b/src/v1/streamcipher/ctr.rs @@ -0,0 +1,248 @@ +// 6.5 The Counter Mode, (Page-22) +// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + +use aes::{ + cipher::{Iv, IvSizeUser, Key, KeyIvInit, StreamCipher, Unsigned}, + Aes128 as CryptoAes128, + Aes192 as CryptoAes192, + Aes256 as CryptoAes256, +}; +use ctr::Ctr64BE; + +use super::crypto::{ + aes::{Aes128, Aes192, Aes256}, + camellia::{Camellia128, Camellia192, Camellia256}, +}; + +type CryptoAes128Ctr = Ctr64BE; +type CryptoAes192Ctr = Ctr64BE; +type CryptoAes256Ctr = Ctr64BE; + +pub struct Aes128Ctr(CryptoAes128Ctr); + +impl Aes128Ctr { + pub const IV_LEN: usize = ::IvSize::USIZE; + pub const KEY_LEN: usize = Aes128::KEY_LEN; + + pub fn new(key: &[u8], iv: &[u8]) -> Aes128Ctr { + let key = Key::::from_slice(key); + let iv = Iv::::from_slice(iv); + let ctr = CryptoAes128Ctr::new(key, iv); + Aes128Ctr(ctr) + } + + pub fn encryptor_update(&mut self, plaintext_in_ciphertext_out: &mut [u8]) { + self.0.apply_keystream(plaintext_in_ciphertext_out); + } + + pub fn decryptor_update(&mut self, ciphertext_in_plaintext_out: &mut [u8]) { + self.0.apply_keystream(ciphertext_in_plaintext_out); + } +} + +pub struct Aes192Ctr(CryptoAes192Ctr); + +impl Aes192Ctr { + pub const IV_LEN: usize = ::IvSize::USIZE; + pub const KEY_LEN: usize = Aes192::KEY_LEN; + + pub fn new(key: &[u8], iv: &[u8]) -> Aes192Ctr { + let key = Key::::from_slice(key); + let iv = Iv::::from_slice(iv); + let ctr = CryptoAes192Ctr::new(key, iv); + Aes192Ctr(ctr) + } + + pub fn encryptor_update(&mut self, plaintext_in_ciphertext_out: &mut [u8]) { + self.0.apply_keystream(plaintext_in_ciphertext_out); + } + + pub fn decryptor_update(&mut self, ciphertext_in_plaintext_out: &mut [u8]) { + self.0.apply_keystream(ciphertext_in_plaintext_out); + } +} + +pub struct Aes256Ctr(CryptoAes256Ctr); + +impl Aes256Ctr { + pub const IV_LEN: usize = ::IvSize::USIZE; + pub const KEY_LEN: usize = Aes256::KEY_LEN; + + pub fn new(key: &[u8], iv: &[u8]) -> Aes256Ctr { + let key = Key::::from_slice(key); + let iv = Iv::::from_slice(iv); + let ctr = CryptoAes256Ctr::new(key, iv); + Aes256Ctr(ctr) + } + + pub fn encryptor_update(&mut self, plaintext_in_ciphertext_out: &mut [u8]) { + self.0.apply_keystream(plaintext_in_ciphertext_out); + } + + pub fn decryptor_update(&mut self, ciphertext_in_plaintext_out: &mut [u8]) { + self.0.apply_keystream(ciphertext_in_plaintext_out); + } +} + +macro_rules! impl_block_cipher_with_ctr_mode { + ($name:tt, $cipher:tt) => { + #[derive(Clone)] + pub struct $name { + cipher: $cipher, + counter_block: [u8; Self::BLOCK_LEN], + keystream: [u8; Self::BLOCK_LEN], + offset: usize, + } + + impl $name { + pub const BLOCK_LEN: usize = $cipher::BLOCK_LEN; + pub const IV_LEN: usize = $cipher::BLOCK_LEN; + pub const KEY_LEN: usize = $cipher::KEY_LEN; + + pub fn new(key: &[u8], iv: &[u8]) -> Self { + assert_eq!(Self::BLOCK_LEN, 16); + assert_eq!(key.len(), Self::KEY_LEN); + assert_eq!(iv.len(), Self::IV_LEN); + + let cipher = $cipher::new(key); + + let mut counter_block = [0u8; Self::IV_LEN]; + counter_block.copy_from_slice(iv); + + let mut keystream = counter_block.clone(); + cipher.encrypt(&mut keystream); + Self::ctr128(&mut counter_block); + + Self { + cipher, + counter_block, + keystream, + offset: 0usize, + } + } + + // NOTE: OpenSSL 的 CTR 模式把整个 Block 当作计数器。也就是 u128。 + #[inline] + fn ctr128(counter_block: &mut [u8; Self::BLOCK_LEN]) { + let octets = u128::from_be_bytes(*counter_block).wrapping_add(1).to_be_bytes(); + counter_block.copy_from_slice(&octets) + } + + #[inline] + fn process(&mut self, plaintext_or_ciphertext: &mut [u8]) { + for i in 0..plaintext_or_ciphertext.len() { + if self.offset == Self::BLOCK_LEN { + self.keystream = self.counter_block.clone(); + self.cipher.encrypt(&mut self.keystream); + Self::ctr128(&mut self.counter_block); + + self.offset = 0; + } + + plaintext_or_ciphertext[i] ^= self.keystream[self.offset]; + self.offset += 1; + } + } + + pub fn encryptor_update(&mut self, plaintext_in_ciphertext_out: &mut [u8]) { + self.process(plaintext_in_ciphertext_out) + } + + pub fn decryptor_update(&mut self, ciphertext_in_plaintext_out: &mut [u8]) { + self.process(ciphertext_in_plaintext_out) + } + } + }; +} + +impl_block_cipher_with_ctr_mode!(Camellia128Ctr, Camellia128); +impl_block_cipher_with_ctr_mode!(Camellia192Ctr, Camellia192); +impl_block_cipher_with_ctr_mode!(Camellia256Ctr, Camellia256); + +#[test] +fn test_aes128_ctr() { + let key = hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(); + let iv = hex::decode("000102030405060708090a0b0c0d0e0f").unwrap(); + let plaintext = hex::decode( + "\ +6bc1bee22e409f96e93d7e117393172a\ +ae2d8a", + ) + .unwrap(); + + let mut ciphertext = plaintext.clone(); + let mut cipher = Aes128Ctr::new(&key, &iv); + cipher.encryptor_update(&mut ciphertext); + + let mut cleartext = ciphertext.clone(); + let mut cipher = Aes128Ctr::new(&key, &iv); + cipher.decryptor_update(&mut cleartext); + + assert_eq!(&cleartext[..], &plaintext[..]); +} + +// F.5 CTR Example Vectors, (Page-62) +// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf +#[test] +fn test_aes128_ctr_enc() { + // F.5.1 CTR-AES128.Encrypt, (Page-62) + // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + let key = hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(); + let iv = hex::decode("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap(); + let plaintext = hex::decode( + "\ +6bc1bee22e409f96e93d7e117393172a\ +ae2d8a571e03ac9c9eb76fac45af8e51\ +30c81c46a35ce411e5fbc1191a0a52ef\ +f69f2445df4f9b17ad2b417be66c3710", + ) + .unwrap(); + + let mut ciphertext = plaintext.clone(); + let mut cipher = Aes128Ctr::new(&key, &iv); + cipher.encryptor_update(&mut ciphertext); + + assert_eq!( + &ciphertext[..], + &hex::decode( + "\ +874d6191b620e3261bef6864990db6ce\ +9806f66b7970fdff8617187bb9fffdff\ +5ae4df3edbd5d35e5b4f09020db03eab\ +1e031dda2fbe03d1792170a0f3009cee" + ) + .unwrap()[..] + ); +} + +#[test] +fn test_aes128_ctr_dec() { + // F.5.2 CTR-AES128.Decrypt, (Page-63) + // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + let key = hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(); + let iv = hex::decode("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap(); + let ciphertext = hex::decode( + "\ +874d6191b620e3261bef6864990db6ce\ +9806f66b7970fdff8617187bb9fffdff\ +5ae4df3edbd5d35e5b4f09020db03eab\ +1e031dda2fbe03d1792170a0f3009cee", + ) + .unwrap(); + + let mut plaintext = ciphertext.clone(); + let mut cipher = Aes128Ctr::new(&key, &iv); + cipher.decryptor_update(&mut plaintext); + + assert_eq!( + &plaintext[..], + &hex::decode( + "\ +6bc1bee22e409f96e93d7e117393172a\ +ae2d8a571e03ac9c9eb76fac45af8e51\ +30c81c46a35ce411e5fbc1191a0a52ef\ +f69f2445df4f9b17ad2b417be66c3710" + ) + .unwrap()[..] + ); +} diff --git a/src/v1/streamcipher/mod.rs b/src/v1/streamcipher/mod.rs new file mode 100644 index 0000000..9240fce --- /dev/null +++ b/src/v1/streamcipher/mod.rs @@ -0,0 +1,222 @@ +use crate::kind::{CipherCategory, CipherKind}; + +mod cfb; +mod chacha20; +mod crypto; +mod ctr; +mod ofb; +mod rc4; +mod rc4_md5; +mod table; + +pub use self::{cfb::*, chacha20::*, ctr::*, ofb::*, rc4::*, rc4_md5::*, table::*}; + +macro_rules! impl_cipher { + ($name:tt, $kind:tt) => { + impl $name { + fn kind(&self) -> CipherKind { + CipherKind::$kind + } + + fn key_len(&self) -> usize { + self.kind().key_len() + } + + fn iv_len(&self) -> usize { + self.kind().iv_len() + } + + fn encrypt_slice(&mut self, plaintext_in_ciphertext_out: &mut [u8]) { + self.encryptor_update(plaintext_in_ciphertext_out); + } + + fn decrypt_slice(&mut self, ciphertext_in_plaintext_out: &mut [u8]) { + self.decryptor_update(ciphertext_in_plaintext_out); + } + } + }; +} + +impl_cipher!(Table, SS_TABLE); +impl_cipher!(Rc4Md5, SS_RC4_MD5); + +impl_cipher!(Aes128Ctr, AES_128_CTR); +impl_cipher!(Aes192Ctr, AES_192_CTR); +impl_cipher!(Aes256Ctr, AES_256_CTR); + +impl_cipher!(Aes128Cfb1, AES_128_CFB1); +impl_cipher!(Aes128Cfb8, AES_128_CFB8); +impl_cipher!(Aes128Cfb128, AES_128_CFB128); + +impl_cipher!(Aes192Cfb1, AES_192_CFB1); +impl_cipher!(Aes192Cfb8, AES_192_CFB8); +impl_cipher!(Aes192Cfb128, AES_192_CFB128); + +impl_cipher!(Aes256Cfb1, AES_256_CFB1); +impl_cipher!(Aes256Cfb8, AES_256_CFB8); +impl_cipher!(Aes256Cfb128, AES_256_CFB128); + +impl_cipher!(Aes128Ofb, AES_128_OFB); +impl_cipher!(Aes192Ofb, AES_192_OFB); +impl_cipher!(Aes256Ofb, AES_256_OFB); + +impl_cipher!(Camellia128Ctr, CAMELLIA_128_CTR); +impl_cipher!(Camellia192Ctr, CAMELLIA_192_CTR); +impl_cipher!(Camellia256Ctr, CAMELLIA_256_CTR); + +impl_cipher!(Camellia128Cfb1, CAMELLIA_128_CFB1); +impl_cipher!(Camellia128Cfb8, CAMELLIA_128_CFB8); +impl_cipher!(Camellia128Cfb128, CAMELLIA_128_CFB128); + +impl_cipher!(Camellia192Cfb1, CAMELLIA_192_CFB1); +impl_cipher!(Camellia192Cfb8, CAMELLIA_192_CFB8); +impl_cipher!(Camellia192Cfb128, CAMELLIA_192_CFB128); + +impl_cipher!(Camellia256Cfb1, CAMELLIA_256_CFB1); +impl_cipher!(Camellia256Cfb8, CAMELLIA_256_CFB8); +impl_cipher!(Camellia256Cfb128, CAMELLIA_256_CFB128); + +impl_cipher!(Camellia128Ofb, CAMELLIA_128_OFB); +impl_cipher!(Camellia192Ofb, CAMELLIA_192_OFB); +impl_cipher!(Camellia256Ofb, CAMELLIA_256_OFB); + +impl_cipher!(Rc4, RC4); +impl_cipher!(Chacha20, CHACHA20); + +macro_rules! stream_cipher_variant { + ($($name:ident @ $kind:ident,)+) => { + enum StreamCipherInner { + $($name($name),)+ + } + + impl StreamCipherInner { + fn new(kind: CipherKind, key: &[u8], iv: &[u8]) -> Self { + match kind { + $(CipherKind::$kind => StreamCipherInner::$name($name::new(key, iv)),)+ + _ => unreachable!("unrecognized stream cipher kind {:?}", kind), + } + } + } + + impl StreamCipherInner { + fn kind(&self) -> CipherKind { + match *self { + $(StreamCipherInner::$name(ref c) => c.kind(),)+ + } + } + + fn key_len(&self) -> usize { + match *self { + $(StreamCipherInner::$name(ref c) => c.key_len(),)+ + } + } + + fn iv_len(&self) -> usize { + match *self { + $(StreamCipherInner::$name(ref c) => c.iv_len(),)+ + } + } + + fn encrypt_slice(&mut self, plaintext_in_ciphertext_out: &mut [u8]) { + match *self { + $(StreamCipherInner::$name(ref mut c) => c.encrypt_slice(plaintext_in_ciphertext_out),)+ + } + } + + fn decrypt_slice(&mut self, ciphertext_in_plaintext_out: &mut [u8]) { + match *self { + $(StreamCipherInner::$name(ref mut c) => c.decrypt_slice(ciphertext_in_plaintext_out),)+ + } + } + } + }; +} + +stream_cipher_variant! { + Table @ SS_TABLE, + Rc4Md5 @ SS_RC4_MD5, + + Aes128Ctr @ AES_128_CTR, + Aes192Ctr @ AES_192_CTR, + Aes256Ctr @ AES_256_CTR, + + Aes128Cfb1 @ AES_128_CFB1, + Aes128Cfb8 @ AES_128_CFB8, + Aes128Cfb128 @ AES_128_CFB128, + + Aes192Cfb1 @ AES_192_CFB1, + Aes192Cfb8 @ AES_192_CFB8, + Aes192Cfb128 @ AES_192_CFB128, + + Aes256Cfb1 @ AES_256_CFB1, + Aes256Cfb8 @ AES_256_CFB8, + Aes256Cfb128 @ AES_256_CFB128, + + Aes128Ofb @ AES_128_OFB, + Aes192Ofb @ AES_192_OFB, + Aes256Ofb @ AES_256_OFB, + + Camellia128Ctr @ CAMELLIA_128_CTR, + Camellia192Ctr @ CAMELLIA_192_CTR, + Camellia256Ctr @ CAMELLIA_256_CTR, + + Camellia128Cfb1 @ CAMELLIA_128_CFB1, + Camellia128Cfb8 @ CAMELLIA_128_CFB8, + Camellia128Cfb128 @ CAMELLIA_128_CFB128, + + Camellia192Cfb1 @ CAMELLIA_192_CFB1, + Camellia192Cfb8 @ CAMELLIA_192_CFB8, + Camellia192Cfb128 @ CAMELLIA_192_CFB128, + + Camellia256Cfb1 @ CAMELLIA_256_CFB1, + Camellia256Cfb8 @ CAMELLIA_256_CFB8, + Camellia256Cfb128 @ CAMELLIA_256_CFB128, + + Camellia128Ofb @ CAMELLIA_128_OFB, + Camellia192Ofb @ CAMELLIA_192_OFB, + Camellia256Ofb @ CAMELLIA_256_OFB, + + Rc4 @ RC4, + + Chacha20 @ CHACHA20, +} + +pub struct StreamCipher { + cipher: StreamCipherInner, +} + +impl StreamCipher { + pub fn new(kind: CipherKind, key: &[u8], iv: &[u8]) -> Self { + let cipher = StreamCipherInner::new(kind, key, iv); + Self { cipher } + } + + pub fn kind(&self) -> CipherKind { + self.cipher.kind() + } + + pub fn category(&self) -> CipherCategory { + CipherCategory::Stream + } + + pub fn key_len(&self) -> usize { + self.cipher.key_len() + } + + pub fn tag_len(&self) -> usize { + 0 + } + + pub fn iv_len(&self) -> usize { + self.cipher.iv_len() + } + + pub fn encrypt(&mut self, plaintext_in_ciphertext_out: &mut [u8]) { + self.cipher.encrypt_slice(plaintext_in_ciphertext_out); + } + + pub fn decrypt(&mut self, ciphertext_in_plaintext_out: &mut [u8]) -> bool { + self.cipher.decrypt_slice(ciphertext_in_plaintext_out); + true + } +} diff --git a/src/v1/streamcipher/ofb.rs b/src/v1/streamcipher/ofb.rs new file mode 100644 index 0000000..aad6942 --- /dev/null +++ b/src/v1/streamcipher/ofb.rs @@ -0,0 +1,174 @@ +// 6.4 The Output Feedback Mode, (Page-20) +// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf +use super::crypto::{ + aes::{Aes128, Aes192, Aes256}, + camellia::{Camellia128, Camellia192, Camellia256}, +}; + +macro_rules! impl_block_cipher_with_ofb_mode { + ($name:tt, $cipher:tt) => { + #[derive(Clone)] + pub struct $name { + cipher: $cipher, + last_output_block: [u8; Self::BLOCK_LEN], + keystream: [u8; Self::BLOCK_LEN], + offset: usize, + } + + impl $name { + // pub const B: usize = Self::BLOCK_LEN * 8; + pub const BLOCK_LEN: usize = $cipher::BLOCK_LEN; + pub const IV_LEN: usize = $cipher::BLOCK_LEN; + pub const KEY_LEN: usize = $cipher::KEY_LEN; + + // The block size, in bits. + + pub fn new(key: &[u8], iv: &[u8]) -> Self { + assert_eq!(key.len(), Self::KEY_LEN); + assert_eq!(iv.len(), Self::IV_LEN); + + let cipher = $cipher::new(key); + + let mut last_output_block = [0u8; Self::IV_LEN]; + last_output_block.copy_from_slice(iv); + + let mut keystream = last_output_block.clone(); + cipher.encrypt(&mut keystream); + + Self { + cipher, + last_output_block, + keystream, + offset: 0usize, + } + } + + pub fn encryptor_update(&mut self, plaintext_in_ciphertext_out: &mut [u8]) { + for i in 0..plaintext_in_ciphertext_out.len() { + if self.offset == Self::BLOCK_LEN { + self.keystream = self.last_output_block.clone(); + self.cipher.encrypt(&mut self.keystream); + + self.offset = 0; + } + + plaintext_in_ciphertext_out[i] ^= self.keystream[self.offset]; + self.last_output_block[self.offset] = self.keystream[self.offset]; + + self.offset += 1; + } + } + + pub fn decryptor_update(&mut self, ciphertext_in_plaintext_out: &mut [u8]) { + for i in 0..ciphertext_in_plaintext_out.len() { + if self.offset == Self::BLOCK_LEN { + self.keystream = self.last_output_block.clone(); + self.cipher.encrypt(&mut self.keystream); + + self.offset = 0; + } + + self.last_output_block[self.offset] = self.keystream[self.offset]; + ciphertext_in_plaintext_out[i] ^= self.keystream[self.offset]; + + self.offset += 1; + } + } + } + }; +} + +impl_block_cipher_with_ofb_mode!(Aes128Ofb, Aes128); +impl_block_cipher_with_ofb_mode!(Aes192Ofb, Aes192); +impl_block_cipher_with_ofb_mode!(Aes256Ofb, Aes256); +impl_block_cipher_with_ofb_mode!(Camellia128Ofb, Camellia128); +impl_block_cipher_with_ofb_mode!(Camellia192Ofb, Camellia192); +impl_block_cipher_with_ofb_mode!(Camellia256Ofb, Camellia256); + +#[test] +fn test_aes128_ofb() { + let key = hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(); + let iv = hex::decode("000102030405060708090a0b0c0d0e0f").unwrap(); + let plaintext = hex::decode( + "\ +6bc1bee22e409f96e93d7e117393172a\ +ae2d8a", + ) + .unwrap(); + + let mut cipher = Aes128Ofb::new(&key, &iv); + let mut ciphertext = plaintext.clone(); + cipher.encryptor_update(&mut ciphertext); + + let mut cipher = Aes128Ofb::new(&key, &iv); + let mut cleartext = ciphertext.clone(); + cipher.decryptor_update(&mut cleartext); + + assert_eq!(&cleartext[..], &plaintext[..]); +} + +#[test] +fn test_aes128_ofb_enc() { + // F.4.1 OFB-AES128.Encrypt, (Page-59) + // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + let key = hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(); + let iv = hex::decode("000102030405060708090a0b0c0d0e0f").unwrap(); + + let plaintext = hex::decode( + "\ +6bc1bee22e409f96e93d7e117393172a\ +ae2d8a571e03ac9c9eb76fac45af8e51\ +30c81c46a35ce411e5fbc1191a0a52ef\ +f69f2445df4f9b17ad2b417be66c3710", + ) + .unwrap(); + + let mut cipher = Aes128Ofb::new(&key, &iv); + let mut ciphertext = plaintext.clone(); + cipher.encryptor_update(&mut ciphertext); + + assert_eq!( + &ciphertext[..], + &hex::decode( + "\ +3b3fd92eb72dad20333449f8e83cfb4a\ +7789508d16918f03f53c52dac54ed825\ +9740051e9c5fecf64344f7a82260edcc\ +304c6528f659c77866a510d9c1d6ae5e" + ) + .unwrap()[..] + ); +} + +#[test] +fn test_aes128_ofb_dec() { + // F.4.2 OFB-AES128.Decrypt, (Page-60) + // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + let key = hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(); + let iv = hex::decode("000102030405060708090a0b0c0d0e0f").unwrap(); + + let ciphertext = hex::decode( + "\ +3b3fd92eb72dad20333449f8e83cfb4a\ +7789508d16918f03f53c52dac54ed825\ +9740051e9c5fecf64344f7a82260edcc\ +304c6528f659c77866a510d9c1d6ae5e", + ) + .unwrap(); + + let mut cipher = Aes128Ofb::new(&key, &iv); + let mut plaintext = ciphertext.clone(); + cipher.decryptor_update(&mut plaintext); + + assert_eq!( + &plaintext[..], + &hex::decode( + "\ +6bc1bee22e409f96e93d7e117393172a\ +ae2d8a571e03ac9c9eb76fac45af8e51\ +30c81c46a35ce411e5fbc1191a0a52ef\ +f69f2445df4f9b17ad2b417be66c3710" + ) + .unwrap()[..] + ); +} diff --git a/src/v1/streamcipher/rc4.rs b/src/v1/streamcipher/rc4.rs new file mode 100644 index 0000000..3216f65 --- /dev/null +++ b/src/v1/streamcipher/rc4.rs @@ -0,0 +1,37 @@ +//! RC4 Source Code +//! +//! +//! + +use crate::v1::streamcipher::crypto::rc4::Rc4 as CryptoRc4; + +#[derive(Clone)] +pub struct Rc4 { + cipher: CryptoRc4, +} + +impl Rc4 { + pub fn new(key: &[u8], _nonce: &[u8]) -> Self { + let cipher = CryptoRc4::new(key); + + Self { cipher } + } + + pub fn encryptor_update(&mut self, plaintext_in_ciphertext_out: &mut [u8]) { + self.cipher.encrypt_slice(plaintext_in_ciphertext_out); + } + + pub fn decryptor_update(&mut self, ciphertext_in_plaintext_out: &mut [u8]) { + self.cipher.decrypt_slice(ciphertext_in_plaintext_out); + } + + pub const fn key_size() -> usize { + // Defined by Shadowsocks' specification. + 16 + } + + pub const fn nonce_size() -> usize { + // Defined by Shadowsocks' specification. + 0 + } +} diff --git a/src/v1/streamcipher/rc4_md5.rs b/src/v1/streamcipher/rc4_md5.rs new file mode 100644 index 0000000..e1a610b --- /dev/null +++ b/src/v1/streamcipher/rc4_md5.rs @@ -0,0 +1,66 @@ +use md5::{Digest, Md5}; + +use crate::v1::streamcipher::crypto::rc4::Rc4 as CryptoRc4; + +/// Rc4Md5 Cipher +#[derive(Clone)] +pub struct Rc4Md5 { + cipher: CryptoRc4, +} + +impl core::fmt::Debug for Rc4Md5 { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("Rc4Md5").finish() + } +} + +impl Rc4Md5 { + pub fn new(key: &[u8], salt: &[u8]) -> Self { + assert_eq!(salt.len(), Self::nonce_size()); + + let mut m = Md5::new(); + m.update(key); + m.update(salt); + + let key = m.finalize(); + + let cipher = CryptoRc4::new(&key); + + Self { cipher } + } + + pub fn encryptor_update(&mut self, plaintext_in_ciphertext_out: &mut [u8]) { + self.cipher.encrypt_slice(plaintext_in_ciphertext_out) + } + + pub fn decryptor_update(&mut self, ciphertext_in_plaintext_out: &mut [u8]) { + self.cipher.decrypt_slice(ciphertext_in_plaintext_out) + } + + pub const fn key_size() -> usize { + // Defined by Shadowsocks' specification. + 16 + } + + pub const fn nonce_size() -> usize { + // Defined by Shadowsocks' specification. + 16 + } +} + +#[test] +fn test_rc4_md5() { + let key: &[u8] = b"key"; + let nonce: &[u8] = b"abcdefg123abcdef"; + let plaintext: &[u8] = b"abcd1234"; + + let mut ciphertext = plaintext.to_vec(); + let mut cipher = Rc4Md5::new(key, nonce); + cipher.encryptor_update(&mut ciphertext); + + let mut cleartext = ciphertext.clone(); + let mut cipher = Rc4Md5::new(key, nonce); + cipher.decryptor_update(&mut cleartext); + + assert_eq!(&cleartext[..], plaintext); +} diff --git a/src/v1/streamcipher/table.rs b/src/v1/streamcipher/table.rs new file mode 100644 index 0000000..f1856af --- /dev/null +++ b/src/v1/streamcipher/table.rs @@ -0,0 +1,126 @@ +use md5::{Digest, Md5}; + +/// Table cipher +#[derive(Clone)] +pub struct Table { + ebox: [u8; Self::TABLE_SIZE], // Encrypt + dbox: [u8; Self::TABLE_SIZE], // Decrypt +} + +impl core::fmt::Debug for Table { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("Table").finish() + } +} + +impl Table { + const TABLE_SIZE: usize = 256; + + pub fn new(key: &[u8], _nonce: &[u8]) -> Self { + let mut m = Md5::new(); + m.update(key); + let h = m.finalize(); + let a = u64::from_le_bytes([h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]]); + + let mut table = [0u64; Self::TABLE_SIZE]; + + for i in 0..table.len() { + table[i] = i as u64; + } + + for i in 1..1024 { + table.sort_by(|x, y| (a % (*x + i)).cmp(&(a % (*y + i)))) + } + + // EK + let mut ebox = [0u8; Self::TABLE_SIZE]; + for i in 0..Self::TABLE_SIZE { + ebox[i] = table[i] as u8; + } + + // DK + let mut dbox = [0u8; Self::TABLE_SIZE]; + for i in 0..Self::TABLE_SIZE { + dbox[table[i] as usize] = i as u8; + } + + Self { ebox, dbox } + } + + pub fn encryptor_update(&self, plaintext_in_ciphertext_out: &mut [u8]) { + for item in plaintext_in_ciphertext_out.iter_mut() { + *item = self.ebox[*item as usize]; + } + } + + pub fn decryptor_update(&self, ciphertext_in_plaintext_out: &mut [u8]) { + for item in ciphertext_in_plaintext_out { + *item = self.dbox[*item as usize]; + } + } +} + +#[test] +fn test_table() { + let key: &[u8] = b"keykeykk"; + let plaintext: &[u8] = b"hello world"; + + let mut ciphertext = plaintext.to_vec(); + let cipher = Table::new(key, b""); + cipher.encryptor_update(&mut ciphertext); + + let mut cleartext = ciphertext.clone(); + let cipher = Table::new(key, b""); + cipher.decryptor_update(&mut cleartext); + + assert_eq!(&cleartext[..], plaintext); +} + +#[test] +fn test_table_box() { + let key: &[u8] = b"password"; + let ebox: [u8; 256] = [ + 157, 219, 245, 15, 85, 7, 195, 211, 55, 126, 37, 117, 249, 229, 98, 205, 254, 61, 137, 77, 253, 135, 138, 185, + 45, 100, 75, 97, 46, 22, 28, 84, 143, 160, 175, 136, 194, 2, 201, 173, 132, 155, 23, 174, 95, 54, 0, 239, 6, + 153, 180, 34, 149, 26, 19, 101, 203, 247, 214, 111, 127, 119, 81, 177, 53, 142, 13, 216, 115, 241, 202, 73, 48, + 86, 1, 11, 43, 125, 41, 121, 209, 193, 199, 51, 47, 32, 36, 90, 255, 156, 38, 108, 3, 99, 238, 179, 50, 237, + 158, 186, 110, 217, 76, 223, 118, 196, 107, 83, 39, 63, 9, 129, 72, 5, 56, 234, 91, 250, 224, 228, 251, 146, + 170, 151, 21, 10, 171, 114, 154, 172, 58, 78, 140, 197, 67, 35, 130, 92, 12, 31, 189, 166, 122, 29, 123, 113, + 215, 94, 165, 89, 221, 240, 93, 178, 150, 218, 220, 232, 144, 188, 65, 88, 52, 59, 139, 242, 71, 62, 182, 57, + 225, 147, 30, 17, 68, 243, 80, 44, 141, 4, 200, 42, 16, 102, 134, 246, 70, 244, 145, 124, 213, 8, 187, 66, 183, + 191, 40, 103, 162, 74, 87, 148, 230, 25, 120, 60, 233, 18, 176, 227, 184, 112, 20, 131, 109, 152, 14, 163, 49, + 24, 222, 181, 164, 133, 207, 104, 210, 236, 27, 106, 96, 64, 33, 116, 79, 206, 69, 212, 82, 169, 105, 235, 190, + 128, 226, 208, 168, 192, 167, 159, 161, 231, 204, 198, 248, 252, + ]; + let dbox: [u8; 256] = [ + 46, 74, 37, 92, 179, 113, 48, 5, 191, 110, 125, 75, 138, 66, 216, 3, 182, 173, 207, 54, 212, 124, 29, 42, 219, + 203, 53, 228, 30, 143, 172, 139, 85, 232, 51, 135, 86, 10, 90, 108, 196, 78, 181, 76, 177, 24, 28, 84, 72, 218, + 96, 83, 162, 64, 45, 8, 114, 169, 130, 163, 205, 17, 167, 109, 231, 160, 193, 134, 174, 236, 186, 166, 112, 71, + 199, 26, 102, 19, 131, 234, 176, 62, 238, 107, 31, 4, 73, 200, 161, 149, 87, 116, 137, 152, 147, 44, 230, 27, + 14, 93, 25, 55, 183, 197, 225, 240, 229, 106, 91, 214, 100, 59, 211, 145, 127, 68, 233, 11, 104, 61, 204, 79, + 142, 144, 189, 77, 9, 60, 243, 111, 136, 213, 40, 223, 184, 21, 35, 18, 22, 164, 132, 178, 65, 32, 158, 188, + 121, 171, 201, 52, 154, 123, 215, 49, 128, 41, 89, 0, 98, 249, 33, 250, 198, 217, 222, 148, 141, 248, 246, 239, + 122, 126, 129, 39, 43, 34, 208, 63, 153, 95, 50, 221, 168, 194, 210, 23, 99, 192, 159, 140, 242, 195, 247, 81, + 36, 6, 105, 133, 253, 82, 180, 38, 70, 56, 252, 15, 235, 224, 245, 80, 226, 7, 237, 190, 58, 146, 67, 101, 155, + 1, 156, 150, 220, 103, 118, 170, 244, 209, 119, 13, 202, 251, 157, 206, 115, 241, 227, 97, 94, 47, 151, 69, + 165, 175, 187, 2, 185, 57, 254, 12, 117, 120, 255, 20, 16, 88, + ]; + + let cipher = Table::new(key, b""); + assert_eq!(cipher.ebox, ebox); + assert_eq!(cipher.dbox, dbox); +} + +#[test] +fn test_table_encrypt() { + let key: &[u8] = b"password"; + let plain_text: &[u8] = b"hello world"; + let cipher_text: &[u8] = &[118, 217, 39, 39, 129, 143, 228, 129, 56, 39, 110]; + + let mut cipher = Table::new(key, b""); + + let mut text_buffer = plain_text.to_vec(); + cipher.encrypt_slice(&mut text_buffer); + + assert_eq!(cipher_text, text_buffer); +} diff --git a/src/v2/crypto/aes_gcm.rs b/src/v2/crypto/aes_gcm.rs new file mode 100644 index 0000000..026d378 --- /dev/null +++ b/src/v2/crypto/aes_gcm.rs @@ -0,0 +1 @@ +include!("../../v1/aeadcipher/aes_gcm.rs"); diff --git a/src/v2/crypto/chacha20_poly1305.rs b/src/v2/crypto/chacha20_poly1305.rs new file mode 100644 index 0000000..a8ffb8d --- /dev/null +++ b/src/v2/crypto/chacha20_poly1305.rs @@ -0,0 +1 @@ +include!("../../v1/aeadcipher/chacha20_poly1305.rs"); diff --git a/src/v2/crypto/chacha8_poly1305.rs b/src/v2/crypto/chacha8_poly1305.rs new file mode 100644 index 0000000..341f307 --- /dev/null +++ b/src/v2/crypto/chacha8_poly1305.rs @@ -0,0 +1,47 @@ +pub use chacha20poly1305::ChaCha8Poly1305 as CryptoChaCha8Poly1305; +use chacha20poly1305::{ + aead::{generic_array::typenum::Unsigned, AeadCore, AeadInPlace, KeyInit, KeySizeUser}, + Key, + Nonce, + Tag, +}; + +pub struct ChaCha8Poly1305(CryptoChaCha8Poly1305); + +impl ChaCha8Poly1305 { + pub fn new(key: &[u8]) -> ChaCha8Poly1305 { + let key = Key::from_slice(key); + ChaCha8Poly1305(CryptoChaCha8Poly1305::new(key)) + } + + pub fn key_size() -> usize { + ::KeySize::to_usize() + } + + pub fn nonce_size() -> usize { + ::NonceSize::to_usize() + } + + pub fn tag_size() -> usize { + ::TagSize::to_usize() + } + + pub fn encrypt(&self, nonce: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + let nonce = Nonce::from_slice(nonce); + let (plaintext, out_tag) = + plaintext_in_ciphertext_out.split_at_mut(plaintext_in_ciphertext_out.len() - Self::tag_size()); + let tag = self + .0 + .encrypt_in_place_detached(nonce, &[], plaintext) + .expect("CHACHA8_POLY1305 encrypt"); + out_tag.copy_from_slice(tag.as_slice()) + } + + pub fn decrypt(&self, nonce: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + let nonce = Nonce::from_slice(nonce); + let (ciphertext, in_tag) = + ciphertext_in_plaintext_out.split_at_mut(ciphertext_in_plaintext_out.len() - Self::tag_size()); + let in_tag = Tag::from_slice(in_tag); + self.0.decrypt_in_place_detached(nonce, &[], ciphertext, in_tag).is_ok() + } +} diff --git a/src/v2/crypto/mod.rs b/src/v2/crypto/mod.rs new file mode 100644 index 0000000..5f8a956 --- /dev/null +++ b/src/v2/crypto/mod.rs @@ -0,0 +1,18 @@ +//! AEAD 2022 Cryptographic Algorithms + +pub use self::{ + aes_gcm::{Aes128Gcm, Aes256Gcm}, + chacha20_poly1305::ChaCha20Poly1305, + xchacha20_poly1305::XChaCha20Poly1305, +}; +#[cfg(feature = "v2-extra")] +pub use self::{chacha8_poly1305::ChaCha8Poly1305, xchacha8_poly1305::XChaCha8Poly1305}; + +pub mod aes_gcm; +pub mod chacha20_poly1305; +#[cfg(feature = "v2-extra")] +pub mod chacha8_poly1305; +#[allow(dead_code)] +pub mod xchacha20_poly1305; +#[cfg(feature = "v2-extra")] +pub mod xchacha8_poly1305; diff --git a/src/v2/crypto/xchacha20_poly1305.rs b/src/v2/crypto/xchacha20_poly1305.rs new file mode 100644 index 0000000..18e8025 --- /dev/null +++ b/src/v2/crypto/xchacha20_poly1305.rs @@ -0,0 +1 @@ +include!("../../v1/aeadcipher/xchacha20_poly1305.rs"); diff --git a/src/v2/crypto/xchacha8_poly1305.rs b/src/v2/crypto/xchacha8_poly1305.rs new file mode 100644 index 0000000..a38751d --- /dev/null +++ b/src/v2/crypto/xchacha8_poly1305.rs @@ -0,0 +1,47 @@ +pub use chacha20poly1305::XChaCha8Poly1305 as CryptoXChaCha8Poly1305; +use chacha20poly1305::{ + aead::{generic_array::typenum::Unsigned, AeadCore, AeadInPlace, KeyInit, KeySizeUser}, + Key, + Tag, + XNonce, +}; + +pub struct XChaCha8Poly1305(CryptoXChaCha8Poly1305); + +impl XChaCha8Poly1305 { + pub fn new(key: &[u8]) -> XChaCha8Poly1305 { + let key = Key::from_slice(key); + XChaCha8Poly1305(CryptoXChaCha8Poly1305::new(key)) + } + + pub fn key_size() -> usize { + ::KeySize::to_usize() + } + + pub fn nonce_size() -> usize { + ::NonceSize::to_usize() + } + + pub fn tag_size() -> usize { + ::TagSize::to_usize() + } + + pub fn encrypt(&self, nonce: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + let nonce = XNonce::from_slice(nonce); + let (plaintext, out_tag) = + plaintext_in_ciphertext_out.split_at_mut(plaintext_in_ciphertext_out.len() - Self::tag_size()); + let tag = self + .0 + .encrypt_in_place_detached(nonce, &[], plaintext) + .expect("XCHACHA8_POLY1305 encrypt"); + out_tag.copy_from_slice(tag.as_slice()) + } + + pub fn decrypt(&self, nonce: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + let nonce = XNonce::from_slice(nonce); + let (ciphertext, in_tag) = + ciphertext_in_plaintext_out.split_at_mut(ciphertext_in_plaintext_out.len() - Self::tag_size()); + let in_tag = Tag::from_slice(in_tag); + self.0.decrypt_in_place_detached(nonce, &[], ciphertext, in_tag).is_ok() + } +} diff --git a/src/v2/mod.rs b/src/v2/mod.rs new file mode 100644 index 0000000..9b61fc2 --- /dev/null +++ b/src/v2/mod.rs @@ -0,0 +1,8 @@ +//! AEAD 2022 Ciphers + +pub(crate) mod crypto; +pub mod tcp; +pub mod udp; + +/// AEAD2022 protocol Blake3 KDF context +pub const BLAKE3_KEY_DERIVE_CONTEXT: &str = "shadowsocks 2022 session subkey"; diff --git a/src/v2/tcp/mod.rs b/src/v2/tcp/mod.rs new file mode 100644 index 0000000..fb5ed29 --- /dev/null +++ b/src/v2/tcp/mod.rs @@ -0,0 +1,155 @@ +//! AEAD 2022 TCP Ciphers + +#[cfg(feature = "v2-extra")] +use crate::v2::crypto::chacha8_poly1305::ChaCha8Poly1305; +use crate::{ + kind::{CipherCategory, CipherKind}, + v2::{ + crypto::{ + aes_gcm::{Aes128Gcm, Aes256Gcm}, + chacha20_poly1305::ChaCha20Poly1305, + }, + BLAKE3_KEY_DERIVE_CONTEXT, + }, +}; + +enum CipherVariant { + Aes128Gcm(Aes128Gcm), + Aes256Gcm(Aes256Gcm), + ChaCha20Poly1305(ChaCha20Poly1305), + #[cfg(feature = "v2-extra")] + ChaCha8Poly1305(ChaCha8Poly1305), +} + +impl CipherVariant { + fn new(kind: CipherKind, key: &[u8]) -> CipherVariant { + match kind { + CipherKind::AEAD2022_BLAKE3_AES_128_GCM => CipherVariant::Aes128Gcm(Aes128Gcm::new(key)), + CipherKind::AEAD2022_BLAKE3_AES_256_GCM => CipherVariant::Aes256Gcm(Aes256Gcm::new(key)), + CipherKind::AEAD2022_BLAKE3_CHACHA20_POLY1305 => { + CipherVariant::ChaCha20Poly1305(ChaCha20Poly1305::new(key)) + } + #[cfg(feature = "v2-extra")] + CipherKind::AEAD2022_BLAKE3_CHACHA8_POLY1305 => CipherVariant::ChaCha8Poly1305(ChaCha8Poly1305::new(key)), + _ => unreachable!("{:?} is not an AEAD-2022 cipher", kind), + } + } + + fn nonce_size(&self) -> usize { + match *self { + CipherVariant::Aes128Gcm(..) => Aes128Gcm::nonce_size(), + CipherVariant::Aes256Gcm(..) => Aes256Gcm::nonce_size(), + CipherVariant::ChaCha20Poly1305(..) => ChaCha20Poly1305::nonce_size(), + #[cfg(feature = "v2-extra")] + CipherVariant::ChaCha8Poly1305(..) => ChaCha8Poly1305::nonce_size(), + } + } + + fn kind(&self) -> CipherKind { + match *self { + CipherVariant::Aes128Gcm(..) => CipherKind::AEAD2022_BLAKE3_AES_128_GCM, + CipherVariant::Aes256Gcm(..) => CipherKind::AEAD2022_BLAKE3_AES_256_GCM, + CipherVariant::ChaCha20Poly1305(..) => CipherKind::AEAD2022_BLAKE3_CHACHA20_POLY1305, + #[cfg(feature = "v2-extra")] + CipherVariant::ChaCha8Poly1305(..) => CipherKind::AEAD2022_BLAKE3_CHACHA8_POLY1305, + } + } + + fn encrypt(&mut self, nonce: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + match *self { + CipherVariant::Aes128Gcm(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), + CipherVariant::Aes256Gcm(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), + CipherVariant::ChaCha20Poly1305(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), + #[cfg(feature = "v2-extra")] + CipherVariant::ChaCha8Poly1305(ref mut c) => c.encrypt(nonce, plaintext_in_ciphertext_out), + } + } + + fn decrypt(&mut self, nonce: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + match *self { + CipherVariant::Aes128Gcm(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), + CipherVariant::Aes256Gcm(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), + CipherVariant::ChaCha20Poly1305(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), + #[cfg(feature = "v2-extra")] + CipherVariant::ChaCha8Poly1305(ref mut c) => c.decrypt(nonce, ciphertext_in_plaintext_out), + } + } +} + +/// AEAD2022 TCP Cipher +pub struct TcpCipher { + cipher: CipherVariant, + nlen: usize, + nonce: [u8; Self::N_MAX], +} + +impl TcpCipher { + /// Maximum length of nonce + const N_MAX: usize = 24; + + /// Create a new Cipher for TCP protocol + pub fn new(kind: CipherKind, key: &[u8], salt: &[u8]) -> Self { + let key_material = [key, salt].concat(); + + let mut hasher = blake3::Hasher::new_derive_key(BLAKE3_KEY_DERIVE_CONTEXT); + hasher.update(&key_material); + let mut hasher_output = hasher.finalize_xof(); + + let mut derived_key = vec![0u8; kind.key_len()]; + hasher_output.fill(&mut derived_key); + + let cipher = CipherVariant::new(kind, &derived_key); + let nlen = cipher.nonce_size(); + debug_assert!(nlen <= Self::N_MAX); + let nonce = [0u8; Self::N_MAX]; + + Self { cipher, nlen, nonce } + } + + /// Cipher's kind + #[inline(always)] + pub fn kind(&self) -> CipherKind { + self.cipher.kind() + } + + /// Cipher's category, should always be `Aead2022` + #[inline(always)] + pub fn category(&self) -> CipherCategory { + CipherCategory::Aead2022 + } + + /// Tag size + #[inline(always)] + pub fn tag_len(&self) -> usize { + self.cipher.kind().tag_len() + } + + #[inline] + fn increase_nonce(&mut self) { + let mut c = self.nonce[0] as u16 + 1; + self.nonce[0] = c as u8; + c >>= 8; + let mut n = 1; + while n < self.nlen { + c += self.nonce[n] as u16; + self.nonce[n] = c as u8; + c >>= 8; + n += 1; + } + } + + /// Encrypt a packet + pub fn encrypt_packet(&mut self, plaintext_in_ciphertext_out: &mut [u8]) { + let nonce = &self.nonce[..self.nlen]; + self.cipher.encrypt(nonce, plaintext_in_ciphertext_out); + self.increase_nonce(); + } + + /// Decrypt a packet + pub fn decrypt_packet(&mut self, ciphertext_in_plaintext_out: &mut [u8]) -> bool { + let nonce = &self.nonce[..self.nlen]; + let ret = self.cipher.decrypt(nonce, ciphertext_in_plaintext_out); + self.increase_nonce(); + ret + } +} diff --git a/src/v2/udp/aes_gcm.rs b/src/v2/udp/aes_gcm.rs new file mode 100644 index 0000000..4b68841 --- /dev/null +++ b/src/v2/udp/aes_gcm.rs @@ -0,0 +1,56 @@ +//! AEAD 2022 UDP aes-*-gcm Ciphers + +use bytes::{BufMut, BytesMut}; + +use crate::{ + v2::{ + crypto::{Aes128Gcm, Aes256Gcm}, + BLAKE3_KEY_DERIVE_CONTEXT, + }, + CipherKind, +}; + +pub enum Cipher { + Aes128Gcm(Aes128Gcm), + Aes256Gcm(Aes256Gcm), +} + +impl Cipher { + pub fn new(kind: CipherKind, key: &[u8], session_id: u64) -> Cipher { + let mut key_material = BytesMut::with_capacity(key.len() + 8); + key_material.put_slice(key); + key_material.put_u64(session_id); + + let mut hasher = blake3::Hasher::new_derive_key(BLAKE3_KEY_DERIVE_CONTEXT); + hasher.update(&key_material); + let mut hasher_output = hasher.finalize_xof(); + + let mut derived_key = vec![0u8; kind.key_len()]; + hasher_output.fill(&mut derived_key); + + match kind { + CipherKind::AEAD2022_BLAKE3_AES_128_GCM => Cipher::Aes128Gcm(Aes128Gcm::new(&derived_key)), + CipherKind::AEAD2022_BLAKE3_AES_256_GCM => Cipher::Aes256Gcm(Aes256Gcm::new(&derived_key)), + _ => unreachable!("cipher {} is not an AES2022 AES-GCM cipher", kind), + } + } + + pub fn nonce_size() -> usize { + debug_assert!(Aes128Gcm::nonce_size() == Aes256Gcm::nonce_size()); + Aes128Gcm::nonce_size() + } + + pub fn encrypt_packet(&self, salt: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + match *self { + Cipher::Aes128Gcm(ref c) => c.encrypt(salt, plaintext_in_ciphertext_out), + Cipher::Aes256Gcm(ref c) => c.encrypt(salt, plaintext_in_ciphertext_out), + } + } + + pub fn decrypt_packet(&self, salt: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + match *self { + Cipher::Aes128Gcm(ref c) => c.decrypt(salt, ciphertext_in_plaintext_out), + Cipher::Aes256Gcm(ref c) => c.decrypt(salt, ciphertext_in_plaintext_out), + } + } +} diff --git a/src/v2/udp/chacha20_poly1305.rs b/src/v2/udp/chacha20_poly1305.rs new file mode 100644 index 0000000..3c1dee9 --- /dev/null +++ b/src/v2/udp/chacha20_poly1305.rs @@ -0,0 +1,28 @@ +//! AEAD 2022 UDP chacha20-poly1305 Ciphers + +use crate::v2::crypto::XChaCha20Poly1305; + +pub struct Cipher { + cipher: XChaCha20Poly1305, +} + +impl Cipher { + pub fn new(key: &[u8]) -> Cipher { + debug_assert_eq!(key.len(), XChaCha20Poly1305::key_size()); + Cipher { + cipher: XChaCha20Poly1305::new(key), + } + } + + pub fn nonce_size() -> usize { + XChaCha20Poly1305::nonce_size() + } + + pub fn encrypt_packet(&self, salt: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + self.cipher.encrypt(salt, plaintext_in_ciphertext_out); + } + + pub fn decrypt_packet(&self, salt: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + self.cipher.decrypt(salt, ciphertext_in_plaintext_out) + } +} diff --git a/src/v2/udp/chacha8_poly1305.rs b/src/v2/udp/chacha8_poly1305.rs new file mode 100644 index 0000000..a66da44 --- /dev/null +++ b/src/v2/udp/chacha8_poly1305.rs @@ -0,0 +1,28 @@ +//! AEAD 2022 UDP chacha8-poly1305 Ciphers + +use crate::v2::crypto::XChaCha8Poly1305; + +pub struct Cipher { + cipher: XChaCha8Poly1305, +} + +impl Cipher { + pub fn new(key: &[u8]) -> Cipher { + debug_assert_eq!(key.len(), XChaCha8Poly1305::key_size()); + Cipher { + cipher: XChaCha8Poly1305::new(key), + } + } + + pub fn nonce_size() -> usize { + XChaCha8Poly1305::nonce_size() + } + + pub fn encrypt_packet(&self, salt: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + self.cipher.encrypt(salt, plaintext_in_ciphertext_out); + } + + pub fn decrypt_packet(&self, salt: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + self.cipher.decrypt(salt, ciphertext_in_plaintext_out) + } +} diff --git a/src/v2/udp/mod.rs b/src/v2/udp/mod.rs new file mode 100644 index 0000000..fa3af4d --- /dev/null +++ b/src/v2/udp/mod.rs @@ -0,0 +1,93 @@ +//! AEAD 2022 UDP Ciphers + +use crate::{CipherCategory, CipherKind}; + +#[cfg(feature = "v2-extra")] +pub use self::chacha8_poly1305::Cipher as ChaCha8Poly1305Cipher; +pub use self::{aes_gcm::Cipher as AesGcmCipher, chacha20_poly1305::Cipher as ChaCha20Poly1305Cipher}; + +mod aes_gcm; +mod chacha20_poly1305; +#[cfg(feature = "v2-extra")] +mod chacha8_poly1305; + +enum CipherVariant { + AesGcm(AesGcmCipher), + ChaCha20Poly1305(ChaCha20Poly1305Cipher), + #[cfg(feature = "v2-extra")] + ChaCha8Poly1305(ChaCha8Poly1305Cipher), +} + +impl CipherVariant { + fn new(kind: CipherKind, key: &[u8], session_id: u64) -> CipherVariant { + match kind { + CipherKind::AEAD2022_BLAKE3_AES_128_GCM | CipherKind::AEAD2022_BLAKE3_AES_256_GCM => { + CipherVariant::AesGcm(AesGcmCipher::new(kind, key, session_id)) + } + CipherKind::AEAD2022_BLAKE3_CHACHA20_POLY1305 => { + CipherVariant::ChaCha20Poly1305(ChaCha20Poly1305Cipher::new(key)) + } + #[cfg(feature = "v2-extra")] + CipherKind::AEAD2022_BLAKE3_CHACHA8_POLY1305 => { + CipherVariant::ChaCha8Poly1305(ChaCha8Poly1305Cipher::new(key)) + } + _ => unreachable!("Cipher {} is not an AEAD 2022 cipher", kind), + } + } + + fn encrypt_packet(&self, salt: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + match *self { + CipherVariant::AesGcm(ref c) => c.encrypt_packet(salt, plaintext_in_ciphertext_out), + CipherVariant::ChaCha20Poly1305(ref c) => c.encrypt_packet(salt, plaintext_in_ciphertext_out), + #[cfg(feature = "v2-extra")] + CipherVariant::ChaCha8Poly1305(ref c) => c.encrypt_packet(salt, plaintext_in_ciphertext_out), + } + } + + fn decrypt_packet(&self, salt: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + match *self { + CipherVariant::AesGcm(ref c) => c.decrypt_packet(salt, ciphertext_in_plaintext_out), + CipherVariant::ChaCha20Poly1305(ref c) => c.decrypt_packet(salt, ciphertext_in_plaintext_out), + #[cfg(feature = "v2-extra")] + CipherVariant::ChaCha8Poly1305(ref c) => c.decrypt_packet(salt, ciphertext_in_plaintext_out), + } + } +} + +/// AEAD2022 UDP Cipher +pub struct UdpCipher { + cipher: CipherVariant, + kind: CipherKind, +} + +impl UdpCipher { + /// Create a new AEAD2022 UDP Cipher + pub fn new(kind: CipherKind, key: &[u8], session_id: u64) -> UdpCipher { + UdpCipher { + cipher: CipherVariant::new(kind, key, session_id), + kind, + } + } + + /// Cipher's kind + #[inline(always)] + pub fn kind(&self) -> CipherKind { + self.kind + } + + /// Cipher's category, should always be `Aead2022` + #[inline(always)] + pub fn category(&self) -> CipherCategory { + CipherCategory::Aead2022 + } + + /// Encrypt a UDP packet, including packet header + pub fn encrypt_packet(&self, salt: &[u8], plaintext_in_ciphertext_out: &mut [u8]) { + self.cipher.encrypt_packet(salt, plaintext_in_ciphertext_out) + } + + /// Decrypt a UDP packet, including packet header + pub fn decrypt_packet(&self, salt: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool { + self.cipher.decrypt_packet(salt, ciphertext_in_plaintext_out) + } +}