├── .github ├── scripts │ ├── ci-build.sh │ ├── ci-proof.sh │ ├── ci-test.sh │ └── update_test_results.sh └── workflows │ ├── build-linux.yaml │ ├── proof-events.yaml │ └── proof-linux.yaml ├── .gitignore ├── LICENSE ├── README.md ├── SECURITY.md ├── _build └── .gitignore ├── _config.yml ├── alire.lock ├── alire.toml ├── artifacts ├── gnatprove.out └── test_phelix.out ├── doc └── phelix │ ├── IPstatement.txt │ ├── coversheet.txt │ └── phelix.pdf ├── lemmas └── saatana-crypto-lemmas.ads ├── rules ├── coding-standards.txt └── restrictions.ads ├── saatana.gpr ├── src ├── phelix │ ├── saatana-crypto-phelix.adb │ └── saatana-crypto-phelix.ads ├── saatana-crypto.adb ├── saatana-crypto.ads └── saatana.ads └── tests ├── phelix ├── saatana-crypto-phelix-test_vectors.ads ├── test_phelix.adb └── test_phelix_api.adb ├── saatana-crypto-stream_tools.adb └── saatana-crypto-stream_tools.ads /.github/scripts/ci-build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | trap 'echo "ERROR at line ${LINENO} (code: $?)" >&2' ERR 4 | trap 'echo "Interrupted" >&2 ; exit 1' INT 5 | 6 | set -o errexit 7 | set -o nounset 8 | 9 | # For the record 10 | echo ENVIRONMENT: 11 | env | sort 12 | echo ............................ 13 | 14 | echo GNAT VERSION: 15 | gnatls -v 16 | echo ............................ 17 | 18 | # Build test_phelix 19 | gprbuild -j0 -p -P saatana.gpr 20 | -------------------------------------------------------------------------------- /.github/scripts/ci-proof.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | trap 'echo "ERROR at line ${LINENO} (code: $?)" >&2' ERR 4 | trap 'echo "Interrupted" >&2 ; exit 1' INT 5 | 6 | set -o errexit 7 | set -o nounset 8 | 9 | echo "Checking provers..." 10 | 11 | SPARKDIR=/opt/gnat/libexec/spark/bin 12 | 13 | (test -x ${SPARKDIR}/alt-ergo && echo `${SPARKDIR}/alt-ergo -version`) || true 14 | (test -x ${SPARKDIR}/cvc4 && echo `${SPARKDIR}/cvc4 --version`) || true 15 | (test -x ${SPARKDIR}/z3 && echo `${SPARKDIR}/z3 -version`) || true 16 | 17 | gnatprove --assumptions --output-header -U -P saatana.gpr | tee gnatprove.stdout 18 | -------------------------------------------------------------------------------- /.github/scripts/ci-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | trap 'echo "ERROR at line ${LINENO} (code: $?)" >&2' ERR 4 | trap 'echo "Interrupted" >&2 ; exit 1' INT 5 | 6 | set -o errexit 7 | set -o nounset 8 | 9 | echo "Running Phelix tests..." 10 | # Pipe KAT output directly into target file 11 | # Test program runs all KATs plus transmission tests by default, but does minimal output by default. 12 | # We want the full test runs, but also text output, so we specify --verbose (but not --kat-only). 13 | ./_build/test_phelix --verbose > artifacts/test_phelix.out 14 | # Test subprogram sets exit status depending on if all tests succeeded or not. 15 | -------------------------------------------------------------------------------- /.github/scripts/update_test_results.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | trap 'echo "ERROR at line ${LINENO} (code: $?)" >&2' ERR 4 | trap 'echo "Interrupted" >&2 ; exit 1' INT 5 | 6 | set -o errexit 7 | set -o nounset 8 | 9 | # Expect the repository (including credentials) as first and only argument 10 | if [ $# -ne 1 ]; then 11 | echo "Missing repository argument!" 12 | exit 1 13 | fi 14 | 15 | timestamp=`date --iso-8601=minutes` 16 | files="artifacts/test*.out" 17 | for file in ${files} 18 | do 19 | echo ${file} 20 | printf "\nTest run from ${timestamp}.\n" >> ${file} # add timestamp to force changes 21 | done; 22 | git config --local user.email "gh+saatana@heisenbug.eu" 23 | git config --local user.name "Auto Committer" 24 | git pull 25 | git add artifacts 26 | git commit -m "* (Autocommit) Test results." 27 | git push "${1}" 28 | -------------------------------------------------------------------------------- /.github/workflows/build-linux.yaml: -------------------------------------------------------------------------------- 1 | name: "Build Linux" 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'LICENSE' 7 | - 'SECURITY.md' 8 | - 'README.md' 9 | - '_build/**' 10 | - '_config.yml' 11 | - 'artifacts/**' 12 | - 'doc/**' 13 | 14 | jobs: 15 | build: 16 | name: CI on ${{ matrix.tag }} ${{ matrix.year }} 17 | runs-on: ubuntu-latest 18 | 19 | strategy: 20 | matrix: 21 | tag: 22 | - community 23 | year: 24 | - 2020 25 | 26 | steps: 27 | - name: Check out repository 28 | uses: actions/checkout@v2 29 | - name: Install toolchain 30 | uses: ada-actions/toolchain@ce2020 31 | with: 32 | distrib: ${{ matrix.tag }} 33 | target: native 34 | community_year: ${{ matrix.year }} 35 | - name: Build 36 | run: > 37 | bash .github/scripts/ci-build.sh 38 | - name: Run tests 39 | run: > 40 | bash .github/scripts/ci-test.sh 41 | -------------------------------------------------------------------------------- /.github/workflows/proof-events.yaml: -------------------------------------------------------------------------------- 1 | # Triggers a proof run if an issue gets labeled with the specific run_proof label. 2 | name: "Proof Trigger" 3 | 4 | on: 5 | issues: 6 | types: [labeled] 7 | 8 | jobs: 9 | remove_label: 10 | if: contains(github.event.label.name, 'run_proof') 11 | name: Remove Label 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/github-script@v2 16 | with: 17 | github-token: ${{ secrets.GITHUB_TOKEN }} 18 | script: | 19 | github.issues.removeLabel ({ 20 | owner : context.repo.owner, 21 | repo : context.repo.repo, 22 | issue_number : context.issue.number, 23 | name : 'run_proof' 24 | }); 25 | 26 | build: 27 | if: contains(github.event.label.name, 'run_proof') 28 | name: Proof on ${{ matrix.tag }} ${{ matrix.year }} 29 | runs-on: ubuntu-latest 30 | 31 | strategy: 32 | matrix: 33 | tag: 34 | - community 35 | # SPARK is only available on community installation 36 | year: 37 | - 2020 38 | 39 | steps: 40 | - name: Check out repository 41 | uses: actions/checkout@v2 42 | - name: Install toolchain 43 | uses: ada-actions/toolchain@ce2020 44 | with: 45 | distrib: ${{ matrix.tag }} 46 | target: native 47 | community_year: ${{ matrix.year }} 48 | - name: Build 49 | run: > 50 | bash .github/scripts/ci-build.sh 51 | - name: Proof 52 | run: > 53 | bash .github/scripts/ci-proof.sh 54 | - name: Commit and push prove logs to artifacts 55 | run: | 56 | cat _build/gnatprove/gnatprove.out > artifacts/gnatprove.out 57 | git config --local user.email "gh+saatana@heisenbug.eu" 58 | git config --local user.name "Auto Committer" 59 | git pull 60 | git commit artifacts/gnatprove.out -m "* (Autocommit) Prove results." 61 | git push "https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" 62 | - name: Upload gnatprove stdout 63 | uses: actions/upload-artifact@master 64 | with: 65 | name: gnatprove-${{ matrix.tag }}.stdout 66 | path: gnatprove.stdout 67 | - name: TARing gnatprove output 68 | run: | 69 | tar --ignore-failed-read`(for i in "*.ali" "*.cswi" "*.stderr" "*.stdout"; do printf " --exclude=${i}"; done)` -vcf gnatprove-output.tar _build/gnatprove 70 | - name: Upload TAR archive of gnatprove output 71 | uses: actions/upload-artifact@master 72 | with: 73 | name: gnatprove-${{ matrix.tag }}-output.tar 74 | path: gnatprove-output.tar 75 | - name: Run tests 76 | run: | 77 | bash .github/scripts/ci-test.sh 78 | - name: Commit and push test results to artifacts 79 | run: | 80 | .github/scripts/update_test_results.sh "https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" 81 | -------------------------------------------------------------------------------- /.github/workflows/proof-linux.yaml: -------------------------------------------------------------------------------- 1 | name: "Proof Linux" 2 | 3 | on: 4 | schedule: 5 | # Run twice daily 6 | - cron: '42 0,12 * * *' 7 | 8 | jobs: 9 | build: 10 | name: Proof on ${{ matrix.tag }} ${{ matrix.year }} 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | tag: 16 | - community 17 | # SPARK is only available on community installation 18 | year: 19 | - 2020 20 | 21 | steps: 22 | - name: Check out repository 23 | uses: actions/checkout@v2 24 | - name: Install toolchain 25 | uses: ada-actions/toolchain@ce2020 26 | with: 27 | distrib: ${{ matrix.tag }} 28 | target: native 29 | community_year: ${{ matrix.year }} 30 | - name: Build 31 | run: > 32 | bash .github/scripts/ci-build.sh 33 | - name: Proof 34 | run: > 35 | bash .github/scripts/ci-proof.sh 36 | - name: Commit and push prove logs to artifacts 37 | run: | 38 | cat _build/gnatprove/gnatprove.out > artifacts/gnatprove.out 39 | git config --local user.email "gh+saatana@heisenbug.eu" 40 | git config --local user.name "Auto Committer" 41 | git pull 42 | git commit artifacts/gnatprove.out -m "* (Autocommit) Prove results." 43 | git push "https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" 44 | - name: Upload gnatprove stdout 45 | uses: actions/upload-artifact@master 46 | with: 47 | name: gnatprove-${{ matrix.tag }}.stdout 48 | path: gnatprove.stdout 49 | - name: TARing gnatprove output 50 | run: | 51 | tar --ignore-failed-read`(for i in "*.ali" "*.cswi" "*.stderr" "*.stdout"; do printf " --exclude=${i}"; done)` -vcf gnatprove-output.tar _build/gnatprove 52 | - name: Upload TAR archive of gnatprove output 53 | uses: actions/upload-artifact@master 54 | with: 55 | name: gnatprove-${{ matrix.tag }}-output.tar 56 | path: gnatprove-output.tar 57 | - name: Run tests 58 | run: | 59 | bash .github/scripts/ci-test.sh 60 | - name: Commit and push test results to artifacts 61 | run: | 62 | .github/scripts/update_test_results.sh "https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | alire/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://raw.githubusercontent.com/HeisenbugLtd/heisenbugltd.github.io/master/assets/img/saatana/cover.png) 2 | 3 | # SPARK/Ada Algorithms Targeting Advanced Network Applications 4 | A cryptographic framework, proven for correctness in SPARK 5 | 6 | [![](https://github.com/HeisenbugLtd/Saatana/workflows/Build%20Linux/badge.svg)](https://github.com/HeisenbugLtd/Saatana/actions?query=workflow%3A"Build+Linux") 7 | [![](https://github.com/HeisenbugLtd/Saatana/workflows/Proof%20Linux/badge.svg)](https://github.com/HeisenbugLtd/Saatana/actions?query=workflow%3A"Proof+Linux") 8 | [![Alire](https://img.shields.io/endpoint?url=https://alire.ada.dev/badges/saatana.json)](https://alire.ada.dev/crates/saatana.html) 9 | 10 | Requires GNAT Community 2020, as we are making use of SPARK's [Relaxed_Initialization](https://docs.adacore.com/spark2014-docs/html/ug/en/source/specification_features.html#aspect-relaxed-initialization-and-attribute-initialized). 11 | 12 | Algorithms contained 13 | - [Phelix](https://www.schneier.com/academic/archives/2005/01/phelix.html) - Fast Encryption and Authentication in a Single Cryptographic Primitive 14 | 15 | Doug Whiting, Bruce Schneier, Stefan Lucks, and Frédéric Muller 16 | 17 | ECRYPT Stream Cipher Project Report 2005/027, 2005. 18 | 19 | ABSTRACT: Phelix¹ is a high-speed stream cipher with a built-in MAC functionality. It is efficient in both hardware and software. On current Pentium CPUs, Phelix has a per-packet overhead of less than 900 clocks, plus a per-byte cost well under 8 clocks per byte, comparing very favorably with the best AES (encryption-only) implementations, even for small packets. 20 | 21 | ¹ Pronounced "felix" (rhymes with "helix"). 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Security updates are supported for the following versions: 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 2.0.2 | :white_check_mark: | 10 | | 2.0.1 | :white_check_mark: | 11 | | 2.0.0 | :white_check_mark: | 12 | | < 2.0 | :x: | 13 | 14 | ## Reporting a Vulnerability 15 | 16 | Use the issue tracker. In this project, any security vulnerabilities are by definition a serious defect. 17 | -------------------------------------------------------------------------------- /_build/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | 4 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal 2 | logo: "https://raw.githubusercontent.com/HeisenbugLtd/heisenbugltd.github.io/master/assets/img/saatana/cover.png" 3 | show_downloads: true 4 | -------------------------------------------------------------------------------- /alire.lock: -------------------------------------------------------------------------------- 1 | # THIS IS A MACHINE-GENERATED FILE. DO NOT EDIT MANUALLY. 2 | 3 | [solution] 4 | [solution.context] 5 | solved = true 6 | [[solution.state]] 7 | crate = "gnat" 8 | fulfilment = "solved" 9 | pinned = false 10 | transitivity = "direct" 11 | versions = "^2020" 12 | [solution.state.release] 13 | description = "GNAT is a compiler for the Ada programming language" 14 | maintainers = [ 15 | "alejandro@mosteo.com", 16 | ] 17 | maintainers-logins = [ 18 | "mosteo", 19 | ] 20 | name = "gnat" 21 | notes = "Detected at /opt/GNAT/2020/bin/gnat" 22 | version = "2020.0.0" 23 | [solution.state.release.origin] 24 | url = "external:path /opt/GNAT/2020/bin/gnat" 25 | 26 | -------------------------------------------------------------------------------- /alire.toml: -------------------------------------------------------------------------------- 1 | description = "SPARK/Ada Algorithms Targeting Advanced Network Applications" 2 | long-description = """Saatana - SPARK/Ada Algorithms Targeting Advanced Network Applications 3 | 4 | A collection of cryptographic algorithms, proven for correctness in SPARK. 5 | 6 | Currently contains [![Phelix](https://www.schneier.com/academic/archives/2005/01/phelix.html)](https://www.schneier.com/academic/archives/2005/01/phelix.html) 7 | """ 8 | name = "saatana" 9 | version = "2.0.3" 10 | licenses = ["WTFPL"] 11 | website = "https://github.heisenbug.eu/Saatana" 12 | authors = ["Vinzent \"Jellix\" Saranen"] 13 | maintainers = ["vinzent@heisenbug.eu"] 14 | maintainers-logins = ["Jellix"] 15 | executables = ["test_phelix"] 16 | project-files = ["saatana.gpr"] 17 | tags = ["cryptography", "spark"] 18 | 19 | [[depends-on]] 20 | gnat = "^2020" 21 | -------------------------------------------------------------------------------- /artifacts/gnatprove.out: -------------------------------------------------------------------------------- 1 | date : 2024-12-16 02:21:30 2 | gnatprove version : SPARK Community 2020 (20200818) 3 | host : Linux 64 bits 4 | command line : gnatprove --assumptions --output-header -U -P saatana.gpr 5 | Proof_Switches attribute: 6 | test_phelix_api.adb: --prover=Z3 --steps=1 --timeout=3 7 | saatana-crypto.adb: --prover=Z3 --steps=1 --timeout=3 8 | saatana-crypto-phelix.adb: --prover=Z3,CVC4 --steps=3986 --timeout=3 9 | saatana-crypto-lemmas.ads: --prover=Z3 --steps=1 --timeout=3 10 | 11 | 12 | Summary of SPARK analysis 13 | ========================= 14 | 15 | ---------------------------------------------------------------------------------------------------------------------- 16 | SPARK Analysis results Total Flow CodePeer Provers Justified Unproved 17 | ---------------------------------------------------------------------------------------------------------------------- 18 | Data Dependencies 20 20 . . . . 19 | Flow Dependencies 14 14 . . . . 20 | Initialization 18 18 . . . . 21 | Non-Aliasing . . . . . . 22 | Run-time Checks 219 . . 219 (CVC4 0%, Trivial 6%, Z3 94%) . . 23 | Assertions 28 . . 28 (Trivial 7%, Z3 93%) . . 24 | Functional Contracts 35 . . 35 (CVC4 1%, Trivial 11%, Z3 88%) . . 25 | LSP Verification . . . . . . 26 | Termination 4 . . 4 (Z3) . . 27 | Concurrency . . . . . . 28 | ---------------------------------------------------------------------------------------------------------------------- 29 | Total 338 52 (15%) . 286 (85%) . . 30 | 31 | 32 | max steps used for successful proof: 3986 33 | 34 | Analyzed 5 units 35 | in unit saatana, 1 subprograms and packages out of 1 analyzed 36 | Saatana at saatana.ads:18 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (0 checks) 37 | in unit saatana-crypto, 5 subprograms and packages out of 5 analyzed 38 | Saatana.Crypto at saatana-crypto.ads:20 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (0 checks) 39 | absence of run-time errors of Saatana.Crypto fully established 40 | Saatana.Crypto.Oadd at saatana-crypto.ads:71 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (6 checks) 41 | effects on parameters and Global variables of Saatana.Crypto.Oadd fully established 42 | absence of run-time errors of Saatana.Crypto.Oadd fully established 43 | Saatana.Crypto.Oadd.Add_Carry at saatana-crypto.adb:18 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (1 checks) 44 | the postcondition of Saatana.Crypto.Oadd.Add_Carry fully established 45 | effects on parameters and Global variables of Saatana.Crypto.Oadd.Add_Carry fully established 46 | absence of run-time errors of Saatana.Crypto.Oadd.Add_Carry fully established 47 | Saatana.Crypto.To_Stream at saatana-crypto.ads:55 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (21 checks) 48 | the postcondition of Saatana.Crypto.To_Stream depends on 49 | effects on parameters and Global variables of Interfaces.Shift_Right 50 | absence of run-time errors of Interfaces.Shift_Right 51 | effects on parameters and Global variables of Saatana.Crypto.To_Stream depends on 52 | effects on parameters and Global variables of Interfaces.Shift_Right 53 | absence of run-time errors of Saatana.Crypto.To_Stream depends on 54 | effects on parameters and Global variables of Interfaces.Shift_Right 55 | absence of run-time errors of Interfaces.Shift_Right 56 | Saatana.Crypto.To_Unsigned at saatana-crypto.ads:41 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (16 checks) 57 | the postcondition of Saatana.Crypto.To_Unsigned depends on 58 | effects on parameters and Global variables of Interfaces.Shift_Left 59 | absence of run-time errors of Interfaces.Shift_Left 60 | effects on parameters and Global variables of Saatana.Crypto.To_Unsigned depends on 61 | effects on parameters and Global variables of Interfaces.Shift_Left 62 | absence of run-time errors of Saatana.Crypto.To_Unsigned depends on 63 | effects on parameters and Global variables of Interfaces.Shift_Left 64 | absence of run-time errors of Interfaces.Shift_Left 65 | in unit saatana-crypto-lemmas, 1 subprograms and packages out of 1 analyzed 66 | Saatana.Crypto.Lemmas at saatana-crypto-lemmas.ads:10 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (6 checks) 67 | in unit saatana-crypto-phelix, 21 subprograms and packages out of 21 analyzed 68 | Saatana.Crypto.Phelix at saatana-crypto-phelix.ads:48 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (0 checks) 69 | absence of run-time errors of Saatana.Crypto.Phelix fully established 70 | Saatana.Crypto.Phelix.Context at saatana-crypto-phelix.ads:63 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (0 checks) 71 | absence of run-time errors of Saatana.Crypto.Phelix.Context fully established 72 | Saatana.Crypto.Phelix.Ctx_AAD_Len at saatana-crypto-phelix.ads:66 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (0 checks) 73 | effects on parameters and Global variables of Saatana.Crypto.Phelix.Ctx_AAD_Len fully established 74 | absence of run-time errors of Saatana.Crypto.Phelix.Ctx_AAD_Len fully established 75 | Saatana.Crypto.Phelix.Ctx_I at saatana-crypto-phelix.ads:70 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (0 checks) 76 | effects on parameters and Global variables of Saatana.Crypto.Phelix.Ctx_I fully established 77 | absence of run-time errors of Saatana.Crypto.Phelix.Ctx_I fully established 78 | Saatana.Crypto.Phelix.Ctx_Key_Size at saatana-crypto-phelix.ads:74 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (0 checks) 79 | effects on parameters and Global variables of Saatana.Crypto.Phelix.Ctx_Key_Size fully established 80 | absence of run-time errors of Saatana.Crypto.Phelix.Ctx_Key_Size fully established 81 | Saatana.Crypto.Phelix.Ctx_Mac_Size at saatana-crypto-phelix.ads:78 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (0 checks) 82 | effects on parameters and Global variables of Saatana.Crypto.Phelix.Ctx_Mac_Size fully established 83 | absence of run-time errors of Saatana.Crypto.Phelix.Ctx_Mac_Size fully established 84 | Saatana.Crypto.Phelix.Ctx_Msg_Len at saatana-crypto-phelix.ads:82 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (0 checks) 85 | effects on parameters and Global variables of Saatana.Crypto.Phelix.Ctx_Msg_Len fully established 86 | absence of run-time errors of Saatana.Crypto.Phelix.Ctx_Msg_Len fully established 87 | Saatana.Crypto.Phelix.Decrypt_Bytes at saatana-crypto-phelix.ads:279 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (39 checks) 88 | the postcondition of Saatana.Crypto.Phelix.Decrypt_Bytes depends on 89 | effects on parameters and Global variables of Interfaces.Rotate_Left 90 | effects on parameters and Global variables of Interfaces.Shift_Left 91 | effects on parameters and Global variables of Interfaces.Shift_Right 92 | absence of run-time errors of Interfaces.Rotate_Left 93 | absence of run-time errors of Interfaces.Shift_Left 94 | absence of run-time errors of Interfaces.Shift_Right 95 | effects on parameters and Global variables of Saatana.Crypto.Phelix.Decrypt_Bytes depends on 96 | effects on parameters and Global variables of Interfaces.Rotate_Left 97 | effects on parameters and Global variables of Interfaces.Shift_Left 98 | effects on parameters and Global variables of Interfaces.Shift_Right 99 | absence of run-time errors of Saatana.Crypto.Phelix.Decrypt_Bytes depends on 100 | effects on parameters and Global variables of Interfaces.Rotate_Left 101 | effects on parameters and Global variables of Interfaces.Shift_Left 102 | effects on parameters and Global variables of Interfaces.Shift_Right 103 | absence of run-time errors of Interfaces.Rotate_Left 104 | absence of run-time errors of Interfaces.Shift_Left 105 | absence of run-time errors of Interfaces.Shift_Right 106 | Saatana.Crypto.Phelix.Decrypt_Packet at saatana-crypto-phelix.ads:144 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (14 checks) 107 | the postcondition of Saatana.Crypto.Phelix.Decrypt_Packet depends on 108 | effects on parameters and Global variables of Interfaces.Rotate_Left 109 | effects on parameters and Global variables of Interfaces.Shift_Left 110 | effects on parameters and Global variables of Interfaces.Shift_Right 111 | absence of run-time errors of Interfaces.Rotate_Left 112 | absence of run-time errors of Interfaces.Shift_Left 113 | absence of run-time errors of Interfaces.Shift_Right 114 | effects on parameters and Global variables of Saatana.Crypto.Phelix.Decrypt_Packet depends on 115 | effects on parameters and Global variables of Interfaces.Rotate_Left 116 | effects on parameters and Global variables of Interfaces.Shift_Left 117 | effects on parameters and Global variables of Interfaces.Shift_Right 118 | absence of run-time errors of Saatana.Crypto.Phelix.Decrypt_Packet depends on 119 | effects on parameters and Global variables of Interfaces.Rotate_Left 120 | effects on parameters and Global variables of Interfaces.Shift_Left 121 | effects on parameters and Global variables of Interfaces.Shift_Right 122 | absence of run-time errors of Interfaces.Rotate_Left 123 | absence of run-time errors of Interfaces.Shift_Left 124 | absence of run-time errors of Interfaces.Shift_Right 125 | Saatana.Crypto.Phelix.Encrypt_Bytes at saatana-crypto-phelix.ads:249 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (38 checks) 126 | the postcondition of Saatana.Crypto.Phelix.Encrypt_Bytes depends on 127 | effects on parameters and Global variables of Interfaces.Rotate_Left 128 | effects on parameters and Global variables of Interfaces.Shift_Left 129 | effects on parameters and Global variables of Interfaces.Shift_Right 130 | absence of run-time errors of Interfaces.Rotate_Left 131 | absence of run-time errors of Interfaces.Shift_Left 132 | absence of run-time errors of Interfaces.Shift_Right 133 | effects on parameters and Global variables of Saatana.Crypto.Phelix.Encrypt_Bytes depends on 134 | effects on parameters and Global variables of Interfaces.Rotate_Left 135 | effects on parameters and Global variables of Interfaces.Shift_Left 136 | effects on parameters and Global variables of Interfaces.Shift_Right 137 | absence of run-time errors of Saatana.Crypto.Phelix.Encrypt_Bytes depends on 138 | effects on parameters and Global variables of Interfaces.Rotate_Left 139 | effects on parameters and Global variables of Interfaces.Shift_Left 140 | effects on parameters and Global variables of Interfaces.Shift_Right 141 | absence of run-time errors of Interfaces.Rotate_Left 142 | absence of run-time errors of Interfaces.Shift_Left 143 | absence of run-time errors of Interfaces.Shift_Right 144 | Saatana.Crypto.Phelix.Encrypt_Packet at saatana-crypto-phelix.ads:103 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (15 checks) 145 | the postcondition of Saatana.Crypto.Phelix.Encrypt_Packet depends on 146 | effects on parameters and Global variables of Interfaces.Rotate_Left 147 | effects on parameters and Global variables of Interfaces.Shift_Left 148 | effects on parameters and Global variables of Interfaces.Shift_Right 149 | absence of run-time errors of Interfaces.Rotate_Left 150 | absence of run-time errors of Interfaces.Shift_Left 151 | absence of run-time errors of Interfaces.Shift_Right 152 | effects on parameters and Global variables of Saatana.Crypto.Phelix.Encrypt_Packet depends on 153 | effects on parameters and Global variables of Interfaces.Rotate_Left 154 | effects on parameters and Global variables of Interfaces.Shift_Left 155 | effects on parameters and Global variables of Interfaces.Shift_Right 156 | absence of run-time errors of Saatana.Crypto.Phelix.Encrypt_Packet depends on 157 | effects on parameters and Global variables of Interfaces.Rotate_Left 158 | effects on parameters and Global variables of Interfaces.Shift_Left 159 | effects on parameters and Global variables of Interfaces.Shift_Right 160 | absence of run-time errors of Interfaces.Rotate_Left 161 | absence of run-time errors of Interfaces.Shift_Left 162 | absence of run-time errors of Interfaces.Shift_Right 163 | Saatana.Crypto.Phelix.Exclusive_Or at saatana-crypto-phelix.adb:18 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (6 checks) 164 | the postcondition of Saatana.Crypto.Phelix.Exclusive_Or fully established 165 | effects on parameters and Global variables of Saatana.Crypto.Phelix.Exclusive_Or fully established 166 | absence of run-time errors of Saatana.Crypto.Phelix.Exclusive_Or fully established 167 | Saatana.Crypto.Phelix.Finalize at saatana-crypto-phelix.ads:306 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (28 checks) 168 | the postcondition of Saatana.Crypto.Phelix.Finalize depends on 169 | effects on parameters and Global variables of Interfaces.Rotate_Left 170 | effects on parameters and Global variables of Interfaces.Shift_Right 171 | absence of run-time errors of Interfaces.Rotate_Left 172 | absence of run-time errors of Interfaces.Shift_Right 173 | effects on parameters and Global variables of Saatana.Crypto.Phelix.Finalize depends on 174 | effects on parameters and Global variables of Interfaces.Rotate_Left 175 | effects on parameters and Global variables of Interfaces.Shift_Right 176 | absence of run-time errors of Saatana.Crypto.Phelix.Finalize depends on 177 | effects on parameters and Global variables of Interfaces.Rotate_Left 178 | effects on parameters and Global variables of Interfaces.Shift_Right 179 | absence of run-time errors of Interfaces.Rotate_Left 180 | absence of run-time errors of Interfaces.Shift_Right 181 | Saatana.Crypto.Phelix.H at saatana-crypto-phelix.adb:44 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (1 checks) 182 | the postcondition of Saatana.Crypto.Phelix.H depends on 183 | effects on parameters and Global variables of Interfaces.Rotate_Left 184 | absence of run-time errors of Interfaces.Rotate_Left 185 | effects on parameters and Global variables of Saatana.Crypto.Phelix.H depends on 186 | effects on parameters and Global variables of Interfaces.Rotate_Left 187 | absence of run-time errors of Saatana.Crypto.Phelix.H depends on 188 | effects on parameters and Global variables of Interfaces.Rotate_Left 189 | absence of run-time errors of Interfaces.Rotate_Left 190 | Saatana.Crypto.Phelix.Key_Size_32Predicate at saatana-crypto-phelix.ads:60 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (1 checks) 191 | absence of run-time errors of Saatana.Crypto.Phelix.Key_Size_32Predicate fully established 192 | Saatana.Crypto.Phelix.MAC_Size_32Predicate at saatana-crypto-phelix.ads:57 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (1 checks) 193 | absence of run-time errors of Saatana.Crypto.Phelix.MAC_Size_32Predicate fully established 194 | Saatana.Crypto.Phelix.Process_AAD at saatana-crypto-phelix.ads:223 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (17 checks) 195 | the postcondition of Saatana.Crypto.Phelix.Process_AAD depends on 196 | effects on parameters and Global variables of Interfaces.Rotate_Left 197 | effects on parameters and Global variables of Interfaces.Shift_Left 198 | absence of run-time errors of Interfaces.Rotate_Left 199 | absence of run-time errors of Interfaces.Shift_Left 200 | effects on parameters and Global variables of Saatana.Crypto.Phelix.Process_AAD depends on 201 | effects on parameters and Global variables of Interfaces.Rotate_Left 202 | effects on parameters and Global variables of Interfaces.Shift_Left 203 | absence of run-time errors of Saatana.Crypto.Phelix.Process_AAD depends on 204 | effects on parameters and Global variables of Interfaces.Rotate_Left 205 | effects on parameters and Global variables of Interfaces.Shift_Left 206 | absence of run-time errors of Interfaces.Rotate_Left 207 | absence of run-time errors of Interfaces.Shift_Left 208 | Saatana.Crypto.Phelix.Setup_Key at saatana-crypto-phelix.ads:180 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (25 checks) 209 | the postcondition of Saatana.Crypto.Phelix.Setup_Key depends on 210 | effects on parameters and Global variables of Interfaces.Rotate_Left 211 | effects on parameters and Global variables of Interfaces.Shift_Left 212 | absence of run-time errors of Interfaces.Rotate_Left 213 | absence of run-time errors of Interfaces.Shift_Left 214 | effects on parameters and Global variables of Saatana.Crypto.Phelix.Setup_Key depends on 215 | effects on parameters and Global variables of Interfaces.Rotate_Left 216 | effects on parameters and Global variables of Interfaces.Shift_Left 217 | absence of run-time errors of Saatana.Crypto.Phelix.Setup_Key depends on 218 | effects on parameters and Global variables of Interfaces.Rotate_Left 219 | effects on parameters and Global variables of Interfaces.Shift_Left 220 | absence of run-time errors of Interfaces.Rotate_Left 221 | absence of run-time errors of Interfaces.Shift_Left 222 | Saatana.Crypto.Phelix.Setup_Key_Called at saatana-crypto-phelix.ads:88 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (0 checks) 223 | effects on parameters and Global variables of Saatana.Crypto.Phelix.Setup_Key_Called fully established 224 | absence of run-time errors of Saatana.Crypto.Phelix.Setup_Key_Called fully established 225 | Saatana.Crypto.Phelix.Setup_Nonce at saatana-crypto-phelix.ads:199 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (7 checks) 226 | the postcondition of Saatana.Crypto.Phelix.Setup_Nonce depends on 227 | effects on parameters and Global variables of Interfaces.Rotate_Left 228 | effects on parameters and Global variables of Interfaces.Shift_Left 229 | absence of run-time errors of Interfaces.Rotate_Left 230 | absence of run-time errors of Interfaces.Shift_Left 231 | effects on parameters and Global variables of Saatana.Crypto.Phelix.Setup_Nonce depends on 232 | effects on parameters and Global variables of Interfaces.Rotate_Left 233 | effects on parameters and Global variables of Interfaces.Shift_Left 234 | absence of run-time errors of Saatana.Crypto.Phelix.Setup_Nonce depends on 235 | effects on parameters and Global variables of Interfaces.Rotate_Left 236 | effects on parameters and Global variables of Interfaces.Shift_Left 237 | absence of run-time errors of Interfaces.Rotate_Left 238 | absence of run-time errors of Interfaces.Shift_Left 239 | Saatana.Crypto.Phelix.Setup_Nonce_Called at saatana-crypto-phelix.ads:92 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (0 checks) 240 | effects on parameters and Global variables of Saatana.Crypto.Phelix.Setup_Nonce_Called fully established 241 | absence of run-time errors of Saatana.Crypto.Phelix.Setup_Nonce_Called fully established 242 | in unit test_phelix_api, 1 subprograms and packages out of 1 analyzed 243 | Test_Phelix_API at test_phelix_api.adb:26 flow analyzed (0 errors, 0 checks and 0 warnings) and proved (44 checks) 244 | effects on parameters and Global variables of Test_Phelix_API depends on 245 | effects on parameters and Global variables of Ada.Command_Line.Set_Exit_Status 246 | effects on parameters and Global variables of Interfaces.Rotate_Left 247 | effects on parameters and Global variables of Interfaces.Shift_Left 248 | effects on parameters and Global variables of Interfaces.Shift_Right 249 | absence of run-time errors of Test_Phelix_API depends on 250 | effects on parameters and Global variables of Ada.Command_Line.Set_Exit_Status 251 | effects on parameters and Global variables of Interfaces.Rotate_Left 252 | effects on parameters and Global variables of Interfaces.Shift_Left 253 | effects on parameters and Global variables of Interfaces.Shift_Right 254 | absence of run-time errors of Ada.Command_Line.Set_Exit_Status 255 | absence of run-time errors of Interfaces.Rotate_Left 256 | absence of run-time errors of Interfaces.Shift_Left 257 | absence of run-time errors of Interfaces.Shift_Right 258 | -------------------------------------------------------------------------------- /doc/phelix/IPstatement.txt: -------------------------------------------------------------------------------- 1 | D. Intellectual Property Statement 2 | 3 | Phelix is a public domain algorithm. There are no fees or royalties 4 | required for its use. The authors are not aware of any patent or pending 5 | patent by any party that would cover the use of Phelix. 6 | 7 | -------------------------------------------------------------------------------- /doc/phelix/coversheet.txt: -------------------------------------------------------------------------------- 1 | A. Cover sheet for Phelix Submission to ECRYPT 2 | 3 | 4 | 1. Name of submitted algorithm: Phelix 5 | 6 | 7 | 2. Type of submitted algorithm: Synchronous stream cipher 8 | with authentication 9 | 10 | Proposed security level: 128-bit. Key length: up to 256 bits. 11 | 12 | Proposed environment: Any. 13 | 14 | 15 | 3. Principle Submitter: Douglas Whiting 16 | Telephone: +1-760-827-4502 17 | Fax: +1-760-930-9115 18 | Organization: Hifn, Inc. 19 | Postal Address: 5973 Avenida Encinas, Suite 110, 20 | Carlsbad, California 92009 USA 21 | E-mail Address: dwhiting@hifn.com 22 | 23 | 24 | 4. Name of auxiliary submitter: Bruce Schneier 25 | 26 | 27 | 5. Name of algorithm inventors: Douglas Whiting, Bruce Schneier, 28 | John Kelsey, Stefan Lucks, Tadayoshi Kohno 29 | 30 | 31 | 6. Name of owner of the algorithm: Public domain 32 | 33 | 34 | 7. Signature of submitter: _________________________________________ 35 | 36 | 37 | 8. Backup point of contact: Bruce Schneier, 38 | Telephone: +1-650-404-2400 39 | Fax: +1-650-903-0461 40 | Organization: Counterpane Internet Security 41 | Postal Address: 1090A La Avenida 42 | Mountain View, CA 94043 USA 43 | E-mail Address: schneier@counterpane.com 44 | -------------------------------------------------------------------------------- /doc/phelix/phelix.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeisenbugLtd/Saatana/7ba07e735498de39216a30479c3d2cc0817f03ac/doc/phelix/phelix.pdf -------------------------------------------------------------------------------- /lemmas/saatana-crypto-lemmas.ads: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------ 2 | -- Copyright (C) 2020 by Heisenbug Ltd. (gh+saatana@heisenbug.eu) 3 | -- 4 | -- This work is free. You can redistribute it and/or modify it under the 5 | -- terms of the Do What The Fuck You Want To Public License, Version 2, 6 | -- as published by Sam Hocevar. See the LICENSE file for more details. 7 | ------------------------------------------------------------------------------ 8 | pragma License (Unrestricted); 9 | 10 | private package Saatana.Crypto.Lemmas with 11 | SPARK_Mode => On 12 | is 13 | 14 | -- 15 | -- Prove additional properties of the types declared in the parent 16 | -- package. 17 | -- 18 | -- These proofs are not actively used anywhere, but they should help 19 | -- building confidence in the correctness of certain subprograms. 20 | 21 | --------------------------------------------------------------------- 22 | -- Stream conversions 23 | --------------------------------------------------------------------- 24 | 25 | -- Fully prove bijectivity of conversion subprogram(s). 26 | 27 | -- Part I 28 | 29 | -- Converting a Word_32 into a stream representation and converting 30 | -- the stream back into a Word_32 should result in the same value. 31 | pragma Assert (for all W in Word_32'Range => 32 | To_Unsigned (General_Stream'(To_Stream (W))) = W); 33 | 34 | -- Part II 35 | 36 | -- The inverse of the above is harder to accomplish, because there 37 | -- seems no easy way to write a quantifiying expression for 38 | -- different length arrays (at least none of the ones I could think 39 | -- of are less complex than the relatively simple five different 40 | -- expressions below). 41 | 42 | -- Luckily, the number of possibilities here is low, so let's simply 43 | -- prove all cases one by one. 44 | 45 | -- Proof for Stream'Length = 4 46 | pragma Assert (for all A in Byte => 47 | (for all B in Byte => 48 | (for all C in Byte => 49 | (for all D in Byte => 50 | General_Stream'(To_Stream (To_Unsigned (General_Stream'(0 => A, 1 => B, 2 => C, 3 => D)))) = 51 | General_Stream'(0 => A, 1 => B, 2 => C, 3 => D))))); 52 | 53 | -- Proof for Stream'Length = 3 54 | pragma Assert (for all A in Byte => 55 | (for all B in Byte => 56 | (for all C in Byte => 57 | General_Stream'(To_Stream (To_Unsigned (General_Stream'(0 => A, 1 => B, 2 => C)))) = 58 | General_Stream'(0 => A, 1 => B, 2 => C, 3 => 0)))); 59 | 60 | -- Proof for Stream'Length = 2 61 | pragma Assert (for all A in Byte => 62 | (for all B in Byte => 63 | General_Stream'(To_Stream (To_Unsigned (General_Stream'(0 => A, 1 => B)))) = 64 | General_Stream'(0 => A, 1 => B, 2 .. 3 => 0))); 65 | 66 | -- Proof for Stream'Length = 1 67 | pragma Assert (for all A in Byte => 68 | General_Stream'(To_Stream (To_Unsigned (General_Stream'(0 => A)))) = 69 | General_Stream'(0 => A, 1 .. 3 => 0)); 70 | 71 | -- Proof for Stream'Length = 0 (i.e. the empty stream). 72 | pragma Assert (General_Stream'(To_Stream (To_Unsigned (General_Stream'(1 .. 0 => 0)))) = 73 | General_Stream'(0 .. 3 => 0)); 74 | 75 | end Saatana.Crypto.Lemmas; 76 | -------------------------------------------------------------------------------- /rules/coding-standards.txt: -------------------------------------------------------------------------------- 1 | -RAnonymous_Subtypes 2 | +RFloat_Equality_Checks 3 | +RGenerics_In_Subprograms 4 | +RImplicit_IN_Mode_Parameters 5 | +RDirect_Calls_To_Primitives 6 | +RVisible_Components 7 | +RImplicit_SMALL_For_Fixed_Point_Types 8 | -RNo_Scalar_Storage_Order_Specified 9 | -RPredefined_Numeric_Types 10 | -RLocal_Packages 11 | +RDeeply_Nested_Generics:2 12 | +RNon_Visible_Exceptions 13 | +RRaising_External_Exceptions 14 | +RAnonymous_Arrays 15 | +REnumeration_Ranges_In_CASE_Statements 16 | +RExceptions_As_Control_Flow 17 | +RExits_From_Conditional_Loops 18 | +REXIT_Statements_With_No_Loop_Name 19 | +RGOTO_Statements 20 | +RImproper_Returns 21 | +ROTHERS_In_CASE_Statements 22 | +ROTHERS_In_Exception_Handlers 23 | +ROverly_Nested_Control_Structures:3 24 | +RPositional_Actuals_For_Defaulted_Generic_Parameters 25 | +RPositional_Actuals_For_Defaulted_Parameters 26 | -RPositional_Components 27 | +RPositional_Generic_Parameters 28 | -RPositional_Parameters 29 | +RRecursive_Subprograms 30 | +RUnnamed_Blocks_And_Loops 31 | +RUSE_PACKAGE_Clauses 32 | +RMisnamed_Controlling_Parameters 33 | +RUncommented_BEGIN_In_Package_Bodies 34 | +RMultiple_Entries_In_Protected_Definitions 35 | +RVolatile_Objects_Without_Address_Clauses 36 | -------------------------------------------------------------------------------- /rules/restrictions.ads: -------------------------------------------------------------------------------- 1 | -- Default is running in SPARK mode. 2 | pragma SPARK_Mode (On); 3 | 4 | -- Restricted run-time (safe tasking subset). This should cover most of the 5 | -- required restrictions from ARM D.7 (tasking restrictions) 6 | pragma Profile (Ravenscar); 7 | 8 | -- SPARK also requires sequential elaboration, so that the elaboration is 9 | -- guaranteed to be finished before tasks are started. 10 | pragma Partition_Elaboration_Policy (Sequential); 11 | 12 | -- 13 | -- Additional restrictions. 14 | -- 15 | 16 | -- High integrity restrictions (ARM H.4) 17 | --pragma Restrictions (No_Allocators); 18 | --pragma Restrictions (No_Local_Allocators); 19 | pragma Restrictions (No_Coextensions); 20 | pragma Restrictions (No_Access_Parameter_Allocators); 21 | pragma Restrictions (Immediate_Reclamation); 22 | 23 | pragma Restrictions (No_Exceptions); 24 | pragma Restrictions (No_Access_Subprograms); 25 | pragma Restrictions (No_Dispatch); 26 | --pragma Restrictions (No_IO); 27 | pragma Restrictions (No_Relative_Delay); 28 | -------------------------------------------------------------------------------- /saatana.gpr: -------------------------------------------------------------------------------- 1 | project Saatana is 2 | 3 | for Source_Dirs use ("src/phelix", "src", "tests/phelix", "tests", "lemmas"); 4 | for Main use ("test_phelix_api", "test_phelix"); 5 | for Object_Dir use "_build"; 6 | 7 | package Pretty_Printer is 8 | for Default_Switches ("ada") use (""); 9 | end Pretty_Printer; 10 | 11 | package Builder is 12 | for Default_Switches ("ada") use ("-s", "-k", "-j0", "-g"); 13 | for Global_Configuration_Pragmas use "rules/restrictions.ads"; 14 | end Builder; 15 | 16 | package Compiler is 17 | for Default_Switches ("ada") use ("-gnatwa", "-gnatyaAbcdefhiklM128noOprsStux3", "-gnato", "-fstack-check", "-gnat12", "-g", "-O2"); 18 | end Compiler; 19 | 20 | package Binder is 21 | for Default_Switches ("ada") use ("-E", "-r"); 22 | end Binder; 23 | 24 | package Linker is 25 | for Default_Switches ("ada") use ("-g"); 26 | end Linker; 27 | 28 | package Prove is 29 | -- Optimized by SPAT(R) 30 | for Proof_Switches ("saatana-crypto-lemmas.ads") use ("--prover=Z3", "--steps=1", "--timeout=3"); 31 | for Proof_Switches ("saatana-crypto-phelix.adb") use ("--prover=Z3,CVC4", "--steps=3986", "--timeout=3"); 32 | for Proof_Switches ("saatana-crypto.adb") use ("--prover=Z3", "--steps=1", "--timeout=3"); 33 | for Proof_Switches ("test_phelix_api.adb") use ("--prover=Z3", "--steps=1", "--timeout=3"); 34 | end Prove; 35 | 36 | package Check is 37 | for Default_Switches ("ada") use ("-rules", "-from=rules/coding-standards.txt"); 38 | end Check; 39 | 40 | end Saatana; 41 | -------------------------------------------------------------------------------- /src/phelix/saatana-crypto-phelix.adb: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------ 2 | -- Copyright (C) 2017-2020 by Heisenbug Ltd. (gh+saatana@heisenbug.eu) 3 | -- 4 | -- This work is free. You can redistribute it and/or modify it under the 5 | -- terms of the Do What The Fuck You Want To Public License, Version 2, 6 | -- as published by Sam Hocevar. See the LICENSE file for more details. 7 | ------------------------------------------------------------------------------ 8 | pragma License (Unrestricted); 9 | 10 | package body Saatana.Crypto.Phelix is 11 | 12 | -- 13 | -- Exclusive_Or 14 | -- 15 | -- Does an in place "xor" per element for the given argument with each 16 | -- element of "Xor_With". 17 | -- 18 | procedure Exclusive_Or (Argument : in out Old_Z_4; 19 | Xor_With : in Old_Z_4) with 20 | Global => null, 21 | Depends => (Argument => (Argument, 22 | Xor_With)), 23 | Post => (for all X in Argument'Range => Argument (X) = (Argument'Old (X) xor Xor_With (X))), 24 | Inline => True; 25 | 26 | -- Phelix algorithm internal constants 27 | OLD_Z_REG : constant := 4; -- which var used for "old" state 28 | MAC_INIT_CNT : constant := 8; -- how many words of pre-MAC mixing 29 | MAC_WORD_CNT : constant := Max_MAC_Size / 32; -- how many words of MAC output 30 | 31 | -- XOR constants 32 | MAC_Magic_XOR : constant Word_32 := 16#912D94F1#; -- magic constant for MAC 33 | AAD_Magic_XOR : constant Word_32 := 16#AADAADAA#; -- magic constant for AAD 34 | 35 | -- 36 | -- H 37 | -- 38 | -- This is Phelix' mixing function. 39 | -- 40 | -- Document and reference implementation split this function into H0 and 41 | -- H1, but as they are always called in sequence, this implementation 42 | -- merges them into a single function. 43 | -- 44 | procedure H (Z : in out State_Words; 45 | Plaintext_Word : in Word_32; 46 | Key_Word : in Word_32) with 47 | Global => null, 48 | Depends => (Z => (Z, Plaintext_Word, Key_Word)), 49 | Post => (Z = (0 => Rotate_Left (Rotate_Left (Z'Old (0) + (Z'Old (3) xor Plaintext_Word), 9) xor 50 | ((Rotate_Left (Z'Old (3), 15) xor (Z'Old (1) + Z'Old (4))) + Key_Word), 51 | 20), 52 | 1 => Rotate_Left ((Rotate_Left (Z'Old (1) + Z'Old (4), 10) xor Rotate_Left (Z'Old (4), 25) + 53 | (Z'Old (2) xor (Z'Old (0) + (Z'Old (3) xor Plaintext_Word)))), 54 | 11), 55 | 2 => Rotate_Left (Rotate_Left ((Z'Old (2) xor (Z'Old (0) + (Z'Old (3) xor Plaintext_Word))), 17) + 56 | (Rotate_Left (Z'Old (0) + 57 | (Z'Old (3) xor Plaintext_Word), 9) xor 58 | ((Rotate_Left (Z'Old (3), 15) xor (Z'Old (1) + Z'Old (4))) + 59 | Key_Word)), 60 | 5), 61 | 3 => Rotate_Left (((Rotate_Left (Z'Old (3), 15)) xor (Z'Old (1) + Z'Old (4))), 30) + 62 | (Rotate_Left ((Z'Old (1) + Z'Old (4)), 10) xor Rotate_Left (Z'Old (4), 25) + 63 | (Z'Old (2) xor (Z'Old (0) + (Z'Old (3) xor Plaintext_Word)))), 64 | 4 => (Rotate_Left ((Rotate_Left (Z'Old (4), 25) + 65 | (Z'Old (2) xor (Z'Old (0) + (Z'Old (3) xor Plaintext_Word)))), 13)) xor 66 | (Rotate_Left ((Z'Old (2) xor (Z'Old (0) + (Z'Old (3) xor Plaintext_Word))), 17) + 67 | ((Rotate_Left ((Z'Old (0) + (Z'Old (3) xor Plaintext_Word)), 9) xor 68 | ((Rotate_Left (Z'Old (3), 15) xor (Z'Old (1) + Z'Old (4))) + 69 | Key_Word)))))); 70 | 71 | type End_Of_Stream_Mask is array (Stream_Count range 1 .. 4) of Word_32; 72 | 73 | MASK : constant End_Of_Stream_Mask := (16#00_00_00_FF#, 74 | 16#00_00_FF_FF#, 75 | 16#00_FF_FF_FF#, 76 | 16#FF_FF_FF_FF#); 77 | 78 | -- 79 | -- Decrypt_Bytes 80 | -- 81 | procedure Decrypt_Bytes (This : in out Context; 82 | Source : in Ciphertext_Stream; 83 | Destination : out Plaintext_Stream) 84 | is 85 | Msg_Len : Stream_Count := Source'Length; 86 | begin 87 | This.CS.Msg_Len := This.CS.Msg_Len + Word_32 (Msg_Len mod 2 ** 32); 88 | This.CS.Z (1) := This.CS.Z (1) xor This.CS.AAD_Xor; -- do the AAD xor, if needed 89 | This.CS.AAD_Xor := 0; -- Next time, the xor will be a nop 90 | 91 | if Source'Length = 0 then 92 | Destination := (others => 0); -- Ensure "initialization". 93 | else 94 | declare 95 | J : Mod_8; 96 | The_Key : Word_32; 97 | Plain_Text : Word_32; 98 | Src_Idx : Stream_Offset := Source'First; 99 | Dst_Idx : Stream_Offset := Destination'First; 100 | Dst_Nxt : Stream_Offset; 101 | begin 102 | while Msg_Len > 0 loop 103 | Decrypt_Word : 104 | declare 105 | Remaining_Bytes : constant Stream_Count := Stream_Count'Min (Msg_Len, 4); 106 | begin 107 | Msg_Len := Msg_Len - Remaining_Bytes; 108 | Dst_Nxt := Dst_Idx + Remaining_Bytes; 109 | 110 | J := Mod_8 (This.CS.I mod 8); 111 | H (Z => This.CS.Z, 112 | Plaintext_Word => 0, 113 | Key_Word => This.KS.X_0 (J)); 114 | 115 | The_Key := This.CS.Z (OLD_Z_REG) + This.CS.Old_Z (Old_State_Words (This.CS.I mod 4)); 116 | 117 | -- If there was a partial word, the resulting Plain_Text needs 118 | -- to be masked as it is used in the further derivation of new Z 119 | -- values. Contrary to the C reference implementation which reads 120 | -- undefined bytes at the end of the stream, here the same result 121 | -- is achieved by masking the Key_Stream value, because 122 | -- To_Unsigned already returns zero for the bytes. 123 | Plain_Text := 124 | To_Unsigned (Source (Src_Idx .. Src_Idx + Remaining_Bytes - 1)) xor (The_Key and MASK (Remaining_Bytes)); 125 | 126 | Destination (Dst_Idx .. Dst_Nxt - 1) := 127 | Plaintext_Stream'(To_Stream (Plain_Text)) (0 .. Remaining_Bytes - 1); 128 | 129 | H (Z => This.CS.Z, 130 | Plaintext_Word => Plain_Text, 131 | Key_Word => This.KS.X_1 (J) + This.CS.I); 132 | This.CS.Old_Z (Old_State_Words (This.CS.I mod 4)) := This.CS.Z (OLD_Z_REG); -- Save The "old" Value 133 | 134 | This.CS.I := This.CS.I + 1; 135 | Src_Idx := Src_Idx + Remaining_Bytes; 136 | Dst_Idx := Dst_Nxt; 137 | end Decrypt_Word; 138 | 139 | pragma Loop_Variant (Decreases => Msg_Len, 140 | Increases => This.CS.I, 141 | Increases => This.CS.Msg_Len, 142 | Increases => Src_Idx, 143 | Increases => Dst_Idx, 144 | Increases => Dst_Nxt); 145 | pragma Loop_Invariant (Src_Idx = Source'Last - Msg_Len + 1 and 146 | Dst_Idx >= Destination'First and Dst_Idx = Destination'Last - Msg_Len + 1 and 147 | Dst_Nxt = Dst_Idx); 148 | pragma Loop_Invariant (for all I in Destination'First .. Dst_Nxt - 1 => 149 | Destination (I)'Initialized); 150 | end loop; 151 | end; 152 | end if; 153 | 154 | -- This assertion is not really needed, it is added here to be explicitly 155 | -- reason about the initialization of the output. 156 | -- And even in case it fails to prove, it may still speed up proof in 157 | -- dependent parts by at least satisfying the post condition. 158 | pragma Assert (Destination'Initialized); 159 | end Decrypt_Bytes; 160 | 161 | -- 162 | -- Decrypt_Packet 163 | -- 164 | procedure Decrypt_Packet (This : in out Context; 165 | Nonce : in Nonce_Stream; 166 | Header : in Plaintext_Stream; 167 | Payload : in Ciphertext_Stream; 168 | Packet : out Plaintext_Stream; 169 | Mac : out MAC_Stream) is 170 | begin 171 | Setup_Nonce (This => This, 172 | Nonce => Nonce); 173 | Process_AAD (This => This, 174 | Aad => Header); 175 | Packet (Packet'First .. Packet'First + Header'Length - 1) := Header; 176 | 177 | Decrypt_Bytes (This => This, 178 | Source => Payload, 179 | Destination => Packet (Packet'First + Header'Length .. Packet'Last)); 180 | 181 | Finalize (This => This, 182 | Mac => Mac); 183 | 184 | -- This assertion is not really needed, it is added here to be explicitly 185 | -- reason about the initialization of the output. 186 | -- And even in case it fails to prove, it may still speed up proof in 187 | -- dependent parts by at least satisfying the post condition. 188 | pragma Assert (Packet'Initialized); 189 | end Decrypt_Packet; 190 | 191 | -- 192 | -- Encrypt_Bytes 193 | -- 194 | procedure Encrypt_Bytes (This : in out Context; 195 | Source : in Plaintext_Stream; 196 | Destination : out Ciphertext_Stream) 197 | is 198 | Msg_Len : Stream_Count := Source'Length; 199 | begin 200 | This.CS.Msg_Len := This.CS.Msg_Len + Word_32 (Msg_Len mod 2 ** 32); 201 | This.CS.Z (1) := This.CS.Z (1) xor This.CS.AAD_Xor; -- do the AAD xor, if needed 202 | This.CS.AAD_Xor := 0; -- Next time, the xor will be a nop 203 | 204 | if Source'Length = 0 then 205 | Destination := (others => 0); -- Ensure "initialization". 206 | else 207 | declare 208 | J : Mod_8; 209 | The_Key : Word_32; 210 | Plain_Text : Word_32; 211 | Cipher_Text : Word_32; 212 | Src_Idx : Stream_Offset := Source'First; 213 | Dst_Idx : Stream_Offset := Destination'First; 214 | Dst_Nxt : Stream_Offset; 215 | begin 216 | while Msg_Len > 0 loop 217 | Encrypt_Word : 218 | declare 219 | Remaining_Bytes : constant Stream_Count := Stream_Count'Min (Msg_Len, 4); 220 | begin 221 | Msg_Len := Msg_Len - Remaining_Bytes; 222 | Dst_Nxt := Dst_Idx + Remaining_Bytes; 223 | 224 | J := Mod_8 (This.CS.I mod 8); 225 | H (Z => This.CS.Z, 226 | Plaintext_Word => 0, 227 | Key_Word => This.KS.X_0 (J)); 228 | The_Key := This.CS.Z (OLD_Z_REG) + This.CS.Old_Z (Old_State_Words (This.CS.I mod 4)); 229 | Plain_Text := To_Unsigned (Source (Src_Idx .. Src_Idx + Remaining_Bytes - 1)); 230 | Cipher_Text := The_Key xor Plain_Text; 231 | Destination (Dst_Idx .. Dst_Nxt - 1) := 232 | Ciphertext_Stream'(To_Stream (Cipher_Text)) (0 .. Remaining_Bytes - 1); 233 | 234 | H (Z => This.CS.Z, 235 | Plaintext_Word => Plain_Text, 236 | Key_Word => This.KS.X_1 (J) + This.CS.I); 237 | This.CS.Old_Z (Old_State_Words (This.CS.I mod 4)) := This.CS.Z (OLD_Z_REG); -- Save The "old" Value 238 | 239 | This.CS.I := This.CS.I + 1; 240 | Src_Idx := Src_Idx + Remaining_Bytes; 241 | Dst_Idx := Dst_Nxt; 242 | end Encrypt_Word; 243 | 244 | pragma Loop_Variant (Decreases => Msg_Len, 245 | Increases => This.CS.I, 246 | Increases => Src_Idx, 247 | Increases => Dst_Idx, 248 | Increases => Dst_Nxt); 249 | pragma Loop_Invariant (Src_Idx = Source'Last - Msg_Len + 1 250 | and Dst_Idx >= Destination'First and Dst_Idx = Destination'Last - Msg_Len + 1 251 | and Dst_Nxt = Dst_Idx); 252 | pragma Loop_Invariant (for all I in Destination'First .. Dst_Nxt - 1 => 253 | Destination (I)'Initialized); 254 | end loop; 255 | end; 256 | end if; 257 | 258 | -- This assertion is not really needed, it is added here to be explicitly 259 | -- reason about the initialization of the output. 260 | -- And even in case it fails to prove, it may still speed up proof in 261 | -- dependent parts by at least satisfying the post condition. 262 | pragma Assert (Destination'Initialized); 263 | end Encrypt_Bytes; 264 | 265 | -- 266 | -- Encrypt_Packet 267 | -- 268 | procedure Encrypt_Packet (This : in out Context; 269 | Nonce : in Nonce_Stream; 270 | Header : in Plaintext_Stream; 271 | Payload : in Plaintext_Stream; 272 | Packet : out Ciphertext_Stream; 273 | Mac : out MAC_Stream) is 274 | begin 275 | Setup_Nonce (This => This, 276 | Nonce => Nonce); 277 | Process_AAD (This => This, 278 | Aad => Header); 279 | Packet (Packet'First .. Packet'First + Header'Length - 1) := Ciphertext_Stream (Header); 280 | 281 | Encrypt_Bytes (This => This, 282 | Source => Payload, 283 | Destination => Packet (Packet'First + Header'Length .. Packet'Last)); 284 | 285 | Finalize (This => This, 286 | Mac => Mac); 287 | 288 | -- This assertion is not really needed, it is added here to be explicitly 289 | -- reason about the initialization of the output. 290 | -- And even in case it fails to prove, it may still speed up proof in 291 | -- dependent parts by at least satisfying the post condition. 292 | pragma Assert (Packet'Initialized); 293 | end Encrypt_Packet; 294 | 295 | -- 296 | -- Exclusive_Or 297 | -- 298 | procedure Exclusive_Or (Argument : in out Old_Z_4; 299 | Xor_With : in Old_Z_4) is 300 | begin 301 | for I in Argument'Range loop 302 | Argument (I) := Argument (I) xor Xor_With (I); 303 | pragma Loop_Invariant (for all S in Argument'First .. I => 304 | Argument (S) = (Argument'Loop_Entry (S) xor Xor_With (S))); 305 | end loop; 306 | end Exclusive_Or; 307 | 308 | -- 309 | -- Finalize 310 | -- 311 | procedure Finalize (This : in out Context; 312 | Mac : out MAC_Stream) 313 | is 314 | MAC_WORDS : constant := MAC_INIT_CNT + MAC_WORD_CNT; 315 | Plain_Text : Word_32; 316 | Mac_Index : Stream_Index; 317 | Tmp : MAC_Stream (0 .. MAC_WORDS * 4 - 1) 318 | with Relaxed_Initialization; 319 | MAC_OFFSET : constant := Tmp'First + MAC_INIT_CNT * 4; 320 | begin 321 | Plain_Text := This.CS.Msg_Len mod 4; 322 | This.CS.Z (0) := This.CS.Z (0) xor MAC_Magic_XOR; 323 | This.CS.Z (4) := This.CS.Z (4) xor Word_32 (This.CS.AAD_Len mod 2 ** 32); 324 | This.CS.Z (2) := This.CS.Z (2) xor Word_32 (This.CS.AAD_Len / 2 ** 32); 325 | This.CS.Z (1) := This.CS.Z (1) xor This.CS.AAD_Xor; -- do this in case Msg_Len = 0 326 | 327 | for K in Word_32 range 0 .. MAC_WORDS - 1 loop 328 | Calculate_MAC_Word : 329 | declare 330 | J : constant Mod_8 := Mod_8 (This.CS.I mod 8); 331 | begin 332 | H (Z => This.CS.Z, 333 | Plaintext_Word => 0, 334 | Key_Word => This.KS.X_0 (J)); 335 | 336 | Store_MAC_Word : 337 | declare 338 | The_Key : constant Word_32 := 339 | This.CS.Z (OLD_Z_REG) + This.CS.Old_Z (Old_State_Words (This.CS.I mod 4)); 340 | begin 341 | Mac_Index := Tmp'First + Stream_Offset (K) * 4; 342 | Tmp (Mac_Index .. Mac_Index + 3) := To_Stream (The_Key xor Plain_Text); 343 | end Store_MAC_Word; 344 | 345 | H (Z => This.CS.Z, 346 | Plaintext_Word => Plain_Text, 347 | Key_Word => This.KS.X_1 (J) + This.CS.I); 348 | This.CS.Old_Z (Old_State_Words (This.CS.I mod 4)) := This.CS.Z (OLD_Z_REG); -- save the "old" value 349 | This.CS.I := This.CS.I + 1; 350 | end Calculate_MAC_Word; 351 | 352 | pragma Loop_Variant (Increases => K, 353 | Increases => Mac_Index, 354 | Increases => This.CS.I); 355 | pragma Loop_Invariant (This.CS.I = This.CS.I'Loop_Entry + K + 1 and 356 | Mac'Length = Stream_Count (This.KS.MAC_Size / 8) and 357 | Mac_Index = Tmp'First + Stream_Offset (K) * 4 and 358 | Mac_Index + 3 in Tmp'Range); 359 | pragma Loop_Invariant (for all I in Tmp'First .. Mac_Index + 3 => 360 | Tmp (I)'Initialized); 361 | end loop; 362 | 363 | -- Copy the relevant bits back to MAC. 364 | Mac := Tmp (MAC_OFFSET .. MAC_OFFSET - 1 + Mac'Length); 365 | 366 | -- We finalized the stream, so the previous Nonce should never be 367 | -- reused. Ensure at least part of this condition by marking the current 368 | -- Nonce as invalid. 369 | This.Setup_Phase := Key_Has_Been_Setup; 370 | end Finalize; 371 | 372 | -- 373 | -- H 374 | -- 375 | procedure H (Z : in out State_Words; 376 | Plaintext_Word : in Word_32; 377 | Key_Word : in Word_32) is 378 | begin 379 | -- First half. 380 | Z (0) := Z (0) + (Z (3) xor Plaintext_Word); 381 | Z (3) := Rotate_Left (Value => Z (3), Amount => 15); 382 | 383 | Z (1) := Z (1) + Z (4); 384 | Z (4) := Rotate_Left (Value => Z (4), Amount => 25); 385 | 386 | Z (2) := Z (2) xor Z (0); 387 | Z (0) := Rotate_Left (Value => Z (0), Amount => 9); 388 | 389 | Z (3) := Z (3) xor Z (1); 390 | Z (1) := Rotate_Left (Value => Z (1), Amount => 10); 391 | 392 | Z (4) := Z (4) + Z (2); 393 | Z (2) := Rotate_Left (Value => Z (2), Amount => 17); 394 | 395 | -- Second half. 396 | Z (0) := Z (0) xor (Z (3) + Key_Word); 397 | Z (3) := Rotate_Left (Value => Z (3), Amount => 30); 398 | 399 | Z (1) := Z (1) xor Z (4); 400 | Z (4) := Rotate_Left (Value => Z (4), Amount => 13); 401 | 402 | Z (2) := Z (2) + Z (0); 403 | Z (0) := Rotate_Left (Value => Z (0), Amount => 20); 404 | 405 | Z (3) := Z (3) + Z (1); 406 | Z (1) := Rotate_Left (Value => Z (1), Amount => 11); 407 | 408 | Z (4) := Z (4) xor Z (2); 409 | Z (2) := Rotate_Left (Value => Z (2), Amount => 5); 410 | end H; 411 | 412 | -- 413 | -- Process_AAD 414 | -- 415 | procedure Process_AAD (This : in out Context; 416 | Aad : in Plaintext_Stream) is 417 | begin 418 | This.CS.AAD_Len := This.CS.AAD_Len + Aad'Length; 419 | 420 | if Aad'Length = 0 then 421 | null; 422 | else 423 | declare 424 | Aad_Len : Stream_Count := Aad'Length; 425 | Src_Idx : Stream_Offset := Aad'First; 426 | begin 427 | while Aad_Len > 0 loop 428 | Process_AAD_Word : 429 | declare 430 | Remaining_Bytes : constant Stream_Count := Stream_Count'Min (Aad_Len, 4); 431 | J : constant Mod_8 := Mod_8 (This.CS.I mod 8); 432 | begin 433 | Aad_Len := Aad_Len - Remaining_Bytes; 434 | 435 | H (Z => This.CS.Z, 436 | Plaintext_Word => 0, 437 | Key_Word => This.KS.X_0 (J)); 438 | 439 | H (Z => This.CS.Z, 440 | Plaintext_Word => To_Unsigned (Aad (Src_Idx .. Src_Idx + Remaining_Bytes - 1)), 441 | Key_Word => This.KS.X_1 (J) + This.CS.I); 442 | 443 | This.CS.Old_Z (Old_State_Words (This.CS.I mod 4)) := This.CS.Z (OLD_Z_REG); -- Save the "old" value 444 | 445 | This.CS.I := This.CS.I + 1; 446 | Src_Idx := Src_Idx + Remaining_Bytes; 447 | end Process_AAD_Word; 448 | 449 | pragma Loop_Variant (Decreases => Aad_Len, 450 | Increases => Src_Idx, 451 | Increases => This.CS.I); 452 | pragma Loop_Invariant (Src_Idx + Aad_Len - 1 = Aad'Last and then 453 | Src_Idx >= Aad'First); 454 | end loop; 455 | end; 456 | end if; 457 | end Process_AAD; 458 | 459 | -- 460 | -- Setup_Key 461 | -- 462 | procedure Setup_Key (This : out Context; 463 | Key : in Key_Stream; 464 | Mac_Size : in MAC_Size_32) 465 | is 466 | Key_Size : constant Key_Size_32 := 8 * Key'Length; 467 | begin 468 | -- These values are going to be overwritten by Setup_Nonce. 469 | -- We initialize them here merely to satisfy the prover. 470 | This.CS := Cipher_State'(Old_Z => (others => 0), 471 | Z => (others => 0), 472 | AAD_Len => 0, 473 | I => 0, 474 | Msg_Len => 0, 475 | AAD_Xor => 0); 476 | This.KS.X_1 := (others => 0); 477 | 478 | -- save key and mac sizes, nonce size is always 128 479 | This.KS.Key_Size := Key_Size; 480 | This.KS.MAC_Size := Mac_Size; 481 | 482 | -- Pre-compute X_1_bump "constant" to save clock cycles during 483 | -- Setup_Nonce. 484 | -- To be honest, I am really not certain if that micro-optimization 485 | -- which I carried over from the C reference implementation is worth the 486 | -- effort. 487 | This.KS.X_1_Bump := Key_Size / 2 + 256 * (Mac_Size mod Max_MAC_Size); 488 | 489 | -- copy key to X_0, in correct endianness 490 | -- Special case for zero length key, there we just set everything to 0. 491 | -- This is done unconditionally to satisfy the prover that X_0 is fully 492 | -- initialized in each path. 493 | This.KS.X_0 := (others => 0); 494 | 495 | if Key'Length /= 0 then 496 | for I in This.KS.X_0'Range loop 497 | Process_Key_Schedule_Word : 498 | declare 499 | Subkey_First : constant Stream_Offset := 500 | Stream_Offset'Min (Key'First + Stream_Offset (I - This.KS.X_0'First) * 4, Key'Last + 1); 501 | Subkey_Last : constant Stream_Index := 502 | Stream_Index'Min (Subkey_First + 3, Key'Last); 503 | begin 504 | This.KS.X_0 (I) := To_Unsigned (Key (Subkey_First .. Subkey_Last)); 505 | pragma Loop_Invariant 506 | (for all S in This.KS.X_0'First .. I => 507 | This.KS.X_0 (S) = 508 | To_Unsigned (Key (Key'First + Stream_Offset (S - This.KS.X_0'First) * 4 .. 509 | Stream_Index'Min (Key'First + Stream_Offset (S - This.KS.X_0'First) * 4 + 3, 510 | Key'Last)))); 511 | end Process_Key_Schedule_Word; 512 | end loop; 513 | end if; 514 | 515 | -- Now process the padded "raw" key, using a Feistel network 516 | Process_Raw_Key : 517 | declare 518 | Z : State_Words; 519 | begin 520 | for I in Mod_8'Range loop 521 | Process_Key_Word : 522 | declare 523 | K : Mod_8; 524 | begin 525 | K := 4 * (I mod 2); 526 | 527 | -- Assignment done via aggregrate rather than array concatenation 528 | -- ("Z := This.KS.X_0 (K .. K + 3) & (Key_Size / 8 + 64);") as 529 | -- this is better handled by the prover. 530 | Z := State_Words'(0 => This.KS.X_0 (K + 0), 531 | 1 => This.KS.X_0 (K + 1), 532 | 2 => This.KS.X_0 (K + 2), 533 | 3 => This.KS.X_0 (K + 3), 534 | 4 => Key_Size / 8 + 64); 535 | 536 | H (Z => Z, 537 | Plaintext_Word => 0, 538 | Key_Word => 0); 539 | H (Z => Z, 540 | Plaintext_Word => 0, 541 | Key_Word => 0); 542 | 543 | K := K + 4; -- mod 8 is done automatically 544 | 545 | Exclusive_Or (Argument => This.KS.X_0 (K .. K + 3), 546 | Xor_With => Z (0 .. 3)); 547 | end Process_Key_Word; 548 | end loop; 549 | end Process_Raw_Key; 550 | 551 | -- Key has been set up. Require a Nonce later. 552 | This.Setup_Phase := Key_Has_Been_Setup; 553 | end Setup_Key; 554 | 555 | -- 556 | -- Setup_Nonce 557 | -- 558 | procedure Setup_Nonce (This : in out Context; 559 | Nonce : in Nonce_Stream) is 560 | begin 561 | -- Initialize subkeys and Z values 562 | for I in Mod_8 range 0 .. 3 loop 563 | Init_Subkey_Word : 564 | declare 565 | N : constant Word_32 := 566 | To_Unsigned (Nonce (Nonce'First + Stream_Offset (I) * 4 .. Nonce'First + Stream_Offset (I) * 4 + 3)); 567 | begin 568 | This.KS.X_1 (I) := This.KS.X_0 (I + 4) + N; 569 | This.KS.X_1 (I + 4) := This.KS.X_0 (I) + (Word_32 (I) - N); 570 | This.CS.Z (I) := This.KS.X_0 (I + 3) xor N; 571 | end Init_Subkey_Word; 572 | end loop; 573 | 574 | This.KS.X_1 (1) := This.KS.X_1 (1) + This.KS.X_1_Bump; -- X' adjustment for i = 1 mod 4 575 | This.KS.X_1 (5) := This.KS.X_1 (5) + This.KS.X_1_Bump; 576 | This.CS.Z (OLD_Z_REG) := This.KS.X_0 (7); 577 | This.CS.AAD_Len := 0; 578 | This.CS.Msg_Len := 0; 579 | 580 | for I in Mod_8'Range loop 581 | -- customized version of loop for zero initialization 582 | H (Z => This.CS.Z, 583 | Plaintext_Word => 0, 584 | Key_Word => This.KS.X_0 (I)); 585 | 586 | H (Z => This.CS.Z, 587 | Plaintext_Word => 0, 588 | Key_Word => This.KS.X_1 (I) + Word_32 (I)); 589 | 590 | This.CS.Old_Z (I mod 4) := This.CS.Z (OLD_Z_REG); -- save the "old" value 591 | end loop; 592 | 593 | This.CS.AAD_Xor := AAD_Magic_XOR; -- perform the AAD xor 594 | This.CS.Z (1) := This.CS.Z (1) xor This.CS.AAD_Xor; 595 | 596 | This.CS.I := 8; 597 | 598 | -- Nonce has been set. 599 | This.Setup_Phase := Nonce_Has_Been_Setup; 600 | end Setup_Nonce; 601 | 602 | end Saatana.Crypto.Phelix; 603 | -------------------------------------------------------------------------------- /src/phelix/saatana-crypto-phelix.ads: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------ 2 | -- Copyright (C) 2017-2020 by Heisenbug Ltd. (gh+saatana@heisenbug.eu) 3 | -- 4 | -- This work is free. You can redistribute it and/or modify it under the 5 | -- terms of the Do What The Fuck You Want To Public License, Version 2, 6 | -- as published by Sam Hocevar. See the LICENSE file for more details. 7 | ------------------------------------------------------------------------------ 8 | pragma License (Unrestricted); 9 | 10 | ------------------------------------------------------------------------------ 11 | -- SPARK implementation of PHELIX. 12 | -- 13 | -- A. Cover sheet for Phelix Submission to ECRYPT 14 | -- 15 | -- 1. Name of submitted algorithm: Phelix 16 | -- 17 | -- 18 | -- 2. Type of submitted algorithm: Synchronous stream cipher with authentication 19 | -- Proposed security level: 128-bit. Key length: up to 256 bits. 20 | -- Proposed environment: Any. 21 | -- 22 | -- 3. Principle Submitter: Douglas Whiting 23 | -- Telephone: +1-760-827-4502 24 | -- Fax: +1-760-930-9115 25 | -- Organization: Hifn, Inc. 26 | -- Postal Address: 5973 Avenida Encinas, Suite 110, 27 | -- Carlsbad, California 92009 USA 28 | -- E-mail Address: dwhiting@hifn.com 29 | -- 30 | -- 4. Name of auxiliary submitter: Bruce Schneier 31 | -- 32 | -- 5. Name of algorithm inventors: Douglas Whiting, Bruce Schneier, 33 | -- John Kelsey, Stefan Lucks, Tadayoshi Kohno 34 | -- 35 | -- 6. Name of owner of the algorithm: Public domain 36 | -- 37 | -- 7. Signature of submitter: _________________________________________ 38 | -- 39 | -- 8. Backup point of contact: Bruce Schneier, 40 | -- Telephone: +1-650-404-2400 41 | -- Fax: +1-650-903-0461 42 | -- Organization: Counterpane Internet Security 43 | -- Postal Address: 1090A La Avenida 44 | -- Mountain View, CA 94043 USA 45 | -- E-mail Address: schneier@counterpane.com 46 | ------------------------------------------------------------------------------ 47 | 48 | package Saatana.Crypto.Phelix with 49 | SPARK_Mode => On, 50 | Pure => True 51 | is 52 | 53 | Max_Nonce_Size : constant := 128; 54 | Max_MAC_Size : constant := 128; 55 | Max_Key_Size : constant := 256; 56 | 57 | subtype MAC_Size_32 is Word_32 range 0 .. Max_MAC_Size with 58 | Dynamic_Predicate => MAC_Size_32 mod 8 = 0; 59 | 60 | subtype Key_Size_32 is Word_32 range 0 .. Max_Key_Size with 61 | Dynamic_Predicate => Key_Size_32 mod 8 = 0; 62 | 63 | type Context is private; 64 | 65 | -- Proof functions. 66 | function Ctx_AAD_Len (Ctx : in Context) return Stream_Count with 67 | Ghost => True, 68 | Global => null; 69 | 70 | function Ctx_I (Ctx : in Context) return Word_32 with 71 | Ghost => True, 72 | Global => null; 73 | 74 | function Ctx_Key_Size (Ctx : in Context) return Key_Size_32 with 75 | Ghost => True, 76 | Global => null; 77 | 78 | function Ctx_Mac_Size (Ctx : in Context) return MAC_Size_32 with 79 | Ghost => True, 80 | Global => null; 81 | 82 | function Ctx_Msg_Len (Ctx : in Context) return Word_32 with 83 | Ghost => True, 84 | Global => null; 85 | 86 | -- As the order in which calls are made is important, we define some proof 87 | -- functions to be used as precondition. 88 | function Setup_Key_Called (Ctx : in Context) return Boolean with 89 | Ghost => True, 90 | Global => null; 91 | 92 | function Setup_Nonce_Called (Ctx : in Context) return Boolean with 93 | Ghost => True, 94 | Global => null; 95 | 96 | -- 97 | -- Encrypt_Packet 98 | -- 99 | -- Using the cipher context This, this subprogram encrypts Payload and 100 | -- stores the Header followed by the encrypted Payload into Packet, and 101 | -- the message authentication code into MAC. 102 | -- 103 | procedure Encrypt_Packet (This : in out Context; 104 | Nonce : in Nonce_Stream; 105 | Header : in Plaintext_Stream; 106 | Payload : in Plaintext_Stream; 107 | Packet : out Ciphertext_Stream; 108 | Mac : out MAC_Stream) with 109 | Global => null, 110 | Depends => (This => (This, 111 | Nonce, 112 | Header, 113 | Payload), 114 | Packet => (Packet, 115 | This, 116 | Nonce, 117 | Header, 118 | Payload), 119 | Mac => (Mac, -- Not really, but SPARK insists. 120 | This, 121 | Nonce, 122 | Header, 123 | Payload)), 124 | Pre => (Setup_Key_Called (This) and then 125 | Header'Initialized and then 126 | Payload'Initialized and then 127 | Header'Length + Payload'Length = Packet'Length and then 128 | Nonce'Length = Max_Nonce_Size / 8 and then 129 | Mac'Length = Stream_Count (Ctx_Mac_Size (This) / 8)), 130 | Post => (Setup_Key_Called (This) = Setup_Key_Called (This'Old) and then 131 | not Setup_Nonce_Called (This) and then 132 | Packet'Initialized); 133 | 134 | -- 135 | -- Decrypt_Packet 136 | -- 137 | -- Using the cipher context This, this subprogram decrypts Payload and 138 | -- stores the Header followed by the decrypted Payload into Packet, and 139 | -- the message authentication code into MAC. 140 | -- 141 | -- The resulting Packet must only be processed if the returned MAC matches 142 | -- the expected one. 143 | -- 144 | procedure Decrypt_Packet (This : in out Context; 145 | Nonce : in Nonce_Stream; 146 | Header : in Plaintext_Stream; 147 | Payload : in Ciphertext_Stream; 148 | Packet : out Plaintext_Stream; 149 | Mac : out MAC_Stream) with 150 | Global => null, 151 | Depends => (This => (This, 152 | Nonce, 153 | Header, 154 | Payload), 155 | Packet => (Packet, -- not really 156 | This, 157 | Nonce, 158 | Header, 159 | Payload), 160 | Mac => (Mac, 161 | This, 162 | Nonce, 163 | Header, 164 | Payload)), 165 | Pre => (Setup_Key_Called (This) and then 166 | Header'Initialized and then 167 | Payload'Initialized and then 168 | Header'Length + Payload'Length = Packet'Length and then 169 | Nonce'Length = Max_Nonce_Size / 8 and then 170 | Mac'Length = Stream_Count (Ctx_Mac_Size (This) / 8)), 171 | Post => (Setup_Key_Called (This) = Setup_Key_Called (This'Old) and then 172 | not Setup_Nonce_Called (This) and then 173 | Packet'Initialized); 174 | 175 | -- 176 | -- Setup_Key 177 | -- 178 | -- Initializes the key schedule of the cipher context This. 179 | -- 180 | procedure Setup_Key (This : out Context; 181 | Key : in Key_Stream; 182 | Mac_Size : in MAC_Size_32) with 183 | Global => null, 184 | Depends => (This => (Key, 185 | Mac_Size)), 186 | Pre => (Key'Length <= Max_Key_Size / 8), -- Support key sizes between 0 and 256 bits 187 | Post => (Setup_Key_Called (This) and then 188 | not Setup_Nonce_Called (This) and then 189 | Ctx_Key_Size (This) = Key'Length * 8 and then 190 | Ctx_Mac_Size (This) = Mac_Size); 191 | 192 | -- 193 | -- Setup_Nonce 194 | -- 195 | -- Updates the internal cipher state with the given Nonce. 196 | -- 197 | -- Setup_Nonce can be called several times to setup a new cipher context. 198 | -- 199 | procedure Setup_Nonce (This : in out Context; 200 | Nonce : in Nonce_Stream) with 201 | Global => null, 202 | Depends => (This => (This, 203 | Nonce)), 204 | Pre => (Setup_Key_Called (This) and then 205 | Nonce'Length = Max_Nonce_Size / 8), 206 | Post => (Setup_Key_Called (This) = Setup_Key_Called (This'Old) and then 207 | Setup_Nonce_Called (This) and then 208 | Ctx_I (This) = 8 and then 209 | Ctx_Key_Size (This) = Ctx_Key_Size (This'Old) and then 210 | Ctx_Mac_Size (This) = Ctx_Mac_Size (This'Old) and then 211 | Ctx_AAD_Len (This) = 0 and then 212 | Ctx_Msg_Len (This) = 0); 213 | 214 | -- 215 | -- Process_AAD 216 | -- 217 | -- Updates the internal cipher state for a proper calculation of the 218 | -- message authentication code for a subsequent decryption or encryption. 219 | -- 220 | -- Process_AAD can be called several times in succession for different 221 | -- parts of the plain text stream. 222 | -- 223 | procedure Process_AAD (This : in out Context; 224 | Aad : in Plaintext_Stream) with 225 | Global => null, 226 | Depends => (This => (This, 227 | Aad)), 228 | Pre => (Aad'Initialized and then 229 | Setup_Nonce_Called (This) and then 230 | Ctx_Msg_Len (This) = 0 and then -- AAD processing must be done first 231 | Ctx_AAD_Len (This) mod 4 = 0 and then -- can only make ONE sub-word call! 232 | Ctx_AAD_Len (This) < Stream_Count'Last - Aad'Length), 233 | Post => (Setup_Key_Called (This) = Setup_Key_Called (This'Old) and then 234 | Setup_Nonce_Called (This) = Setup_Nonce_Called (This'Old) and then 235 | Ctx_AAD_Len (This) = Ctx_AAD_Len (This'Old) + Aad'Length and then 236 | Ctx_Msg_Len (This) = 0 and then 237 | Ctx_Key_Size (This) = Ctx_Key_Size (This'Old) and then 238 | Ctx_Mac_Size (This) = Ctx_Mac_Size (This'Old)); 239 | 240 | -- 241 | -- Encrypt_Bytes 242 | -- 243 | -- Using the cipher context This, this subprogram encrypts the Source into 244 | -- Destination. 245 | -- 246 | -- Encrypt_Bytes can be called several times in succession for different 247 | -- parts of the plaintext. 248 | -- 249 | procedure Encrypt_Bytes (This : in out Context; 250 | Source : in Plaintext_Stream; 251 | Destination : out Ciphertext_Stream) with 252 | Global => null, 253 | Depends => (This => (This, 254 | Source), 255 | Destination => (This, 256 | Destination, 257 | Source)), 258 | Pre => (Source'Initialized and then 259 | Source'Length = Destination'Length and then 260 | Setup_Nonce_Called (This) and then 261 | Ctx_Msg_Len (This) mod 4 = 0), -- Can only make ONE sub-word call! 262 | Post => (Setup_Key_Called (This) = Setup_Key_Called (This'Old) and then 263 | Setup_Nonce_Called (This) = Setup_Nonce_Called (This'Old) and then 264 | Ctx_AAD_Len (This) = Ctx_AAD_Len (This'Old) and then 265 | Ctx_Msg_Len (This) = Ctx_Msg_Len (This'Old) + Word_32 (Source'Length mod 2 ** 32) and then 266 | Ctx_Key_Size (This) = Ctx_Key_Size (This'Old) and then 267 | Ctx_Mac_Size (This) = Ctx_Mac_Size (This'Old) and then 268 | Destination'Initialized); 269 | 270 | -- 271 | -- Decrypt_Bytes 272 | -- 273 | -- Using the cipher context This, this subprogram decrypts the Source into 274 | -- Destination. 275 | -- 276 | -- Decrypt_Bytes can be called several times in succession for different 277 | -- parts of the cipher text. 278 | -- 279 | procedure Decrypt_Bytes (This : in out Context; 280 | Source : in Ciphertext_Stream; 281 | Destination : out Plaintext_Stream) with 282 | Global => null, 283 | Depends => (This => (This, 284 | Source), 285 | Destination => (Destination, 286 | This, 287 | Source)), 288 | Pre => (Source'Initialized and then 289 | Source'Length = Destination'Length and then 290 | Setup_Nonce_Called (This) and then 291 | Ctx_Msg_Len (This) mod 4 = 0), 292 | Post => (Setup_Key_Called (This) = Setup_Key_Called (This'Old) and then 293 | Setup_Nonce_Called (This) = Setup_Nonce_Called (This'Old) and then 294 | Ctx_AAD_Len (This) = Ctx_AAD_Len (This'Old) and then 295 | Ctx_Msg_Len (This) = Ctx_Msg_Len (This'Old) + Word_32 (Source'Length mod 2 ** 32) and then 296 | Ctx_Key_Size (This) = Ctx_Key_Size (This'Old) and then 297 | Ctx_Mac_Size (This) = Ctx_Mac_Size (This'Old) and then 298 | Destination'Initialized); 299 | 300 | -- 301 | -- Finalize 302 | -- 303 | -- Calculates the message authentication code after a decryption or 304 | -- encryption and stores it in Mac. 305 | -- 306 | procedure Finalize (This : in out Context; 307 | Mac : out MAC_Stream) with 308 | Global => null, 309 | Depends => (This => This, 310 | Mac => (Mac, -- This isn't exactly True, but SPARK insists, probably because we rely on Mac'Length 311 | This)), 312 | Pre => (Setup_Nonce_Called (This) and then 313 | Mac'Length = Stream_Count (Ctx_Mac_Size (This) / 8)), 314 | Post => (Setup_Key_Called (This) = Setup_Key_Called (This'Old) and then 315 | not Setup_Nonce_Called (This)); 316 | 317 | private 318 | 319 | type Mod_8 is mod 8; 320 | 321 | subtype Full_State_Words is Mod_8 range 0 .. 4; -- 5 state words 322 | subtype Old_State_Words is Mod_8 range 0 .. 3; -- 4 old state words 323 | 324 | type Unsigned_32_Array is array (Mod_8 range <>) of Word_32; 325 | 326 | -- Several state arrays (old Z, state words, expanded key. 327 | subtype Old_Z_4 is Unsigned_32_Array (Old_State_Words); 328 | subtype State_Words is Unsigned_32_Array (Full_State_Words); 329 | subtype Key_Processing is Unsigned_32_Array (Mod_8); 330 | 331 | type Key_Schedule is 332 | tagged record 333 | Key_Size : Key_Size_32; -- initial key size, in bits 334 | MAC_Size : MAC_Size_32; -- MAC tag size, in bits 335 | X_1_Bump : Word_32; -- 4 * (keySize / 8) + 256 * (MAC_Size mod 128) 336 | X_0 : Key_Processing; 337 | X_1 : Key_Processing; -- processed working key material 338 | end record; 339 | 340 | type Cipher_State is 341 | tagged record 342 | Old_Z : Old_Z_4; -- Previous four Z_4 values for output 343 | Z : State_Words; -- 5 internal state words (160 bits) 344 | AAD_Len : Stream_Count; -- AAD length 345 | I : Word_32; -- block number (modulo 2 ** 32) 346 | Msg_Len : Word_32; -- message length (modulo 2 ** 32) 347 | AAD_Xor : Word_32; -- aadXor constant 348 | end record; 349 | 350 | type Phase is (Uninitialized, Key_Has_Been_Setup, Nonce_Has_Been_Setup); 351 | -- Ensure proper call sequence. State changes are: 352 | -- 353 | -- (Uninitialized) 354 | -- | 355 | -- v 356 | -- (Key_Has_Been_Setup) <-. 357 | -- | | 358 | -- v | 359 | -- (Nonce_Has_Been_Setup) | 360 | -- | | 361 | -- `--------------' 362 | 363 | type Context is 364 | record 365 | KS : Key_Schedule; 366 | CS : Cipher_State; 367 | -- This state variable is merely here to ensure proper call sequences 368 | -- as precondition. 369 | -- Also, we need it to be automatically initialized. 370 | Setup_Phase : Phase := Uninitialized; 371 | end record; 372 | 373 | -- Proof functions 374 | 375 | function Ctx_AAD_Len (Ctx : in Context) return Stream_Count is 376 | (Ctx.CS.AAD_Len); 377 | 378 | function Ctx_I (Ctx : in Context) return Word_32 is 379 | (Ctx.CS.I); 380 | 381 | function Ctx_Key_Size (Ctx : in Context) return Key_Size_32 is 382 | (Ctx.KS.Key_Size); 383 | 384 | function Ctx_Mac_Size (Ctx : in Context) return MAC_Size_32 is 385 | (Ctx.KS.MAC_Size); 386 | 387 | function Ctx_Msg_Len (Ctx : in Context) return Word_32 is 388 | (Ctx.CS.Msg_Len); 389 | 390 | function Setup_Key_Called (Ctx : in Context) return Boolean is 391 | (Ctx.Setup_Phase in Key_Has_Been_Setup .. Nonce_Has_Been_Setup); 392 | 393 | function Setup_Nonce_Called (Ctx : in Context) return Boolean is 394 | (Ctx.Setup_Phase in Nonce_Has_Been_Setup); 395 | 396 | end Saatana.Crypto.Phelix; 397 | -------------------------------------------------------------------------------- /src/saatana-crypto.adb: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------ 2 | -- Copyright (C) 2017-2020 by Heisenbug Ltd. (gh+saatana@heisenbug.eu) 3 | -- 4 | -- This work is free. You can redistribute it and/or modify it under the 5 | -- terms of the Do What The Fuck You Want To Public License, Version 2, 6 | -- as published by Sam Hocevar. See the LICENSE file for more details. 7 | ------------------------------------------------------------------------------ 8 | pragma License (Unrestricted); 9 | 10 | package body Saatana.Crypto is 11 | 12 | -- 13 | -- "+" 14 | -- 15 | function "+" (Left : in Nonce_Stream; 16 | Right : in Nonce_Stream) return Nonce_Stream 17 | is 18 | procedure Add_Carry (Left : in out Byte; 19 | Right : in Byte; 20 | Carry : in out Boolean) with 21 | Inline => True, 22 | Depends => (Left => (Left, 23 | Right, 24 | Carry), 25 | Carry => (Left, 26 | Right)), 27 | Post => (Left = Left'Old + Right + Boolean'Pos (Carry'Old) and then 28 | Carry = (Left'Old + Right < Left'Old)); 29 | 30 | procedure Add_Carry (Left : in out Byte; 31 | Right : in Byte; 32 | Carry : in out Boolean) 33 | is 34 | Old_Carry : constant Boolean := Carry; 35 | begin 36 | Left := Left + Right; 37 | 38 | Carry := Left < Right; 39 | Left := Left + Boolean'Pos (Old_Carry); 40 | end Add_Carry; 41 | 42 | Result : Nonce_Stream := Left; 43 | Carry : Boolean := False; 44 | begin 45 | for Result_Idx in Result'Range loop 46 | Add_Byte_With_Carry : 47 | declare 48 | Operand_Idx : constant Stream_Index := Result_Idx - Result'First + Right'First; 49 | begin 50 | Add_Carry (Left => Result (Result_Idx), 51 | Right => Right (Operand_Idx), 52 | Carry => Carry); 53 | end Add_Byte_With_Carry; 54 | end loop; 55 | 56 | return Result; 57 | end "+"; 58 | 59 | end Saatana.Crypto; 60 | -------------------------------------------------------------------------------- /src/saatana-crypto.ads: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------ 2 | -- Copyright (C) 2017-2020 by Heisenbug Ltd. (gh+saatana@heisenbug.eu) 3 | -- 4 | -- This work is free. You can redistribute it and/or modify it under the 5 | -- terms of the Do What The Fuck You Want To Public License, Version 2, 6 | -- as published by Sam Hocevar. See the LICENSE file for more details. 7 | ------------------------------------------------------------------------------ 8 | pragma License (Unrestricted); 9 | 10 | ------------------------------------------------------------------------------ 11 | -- 12 | -- Crypto root package. 13 | -- 14 | -- Defines encryption stream primitives. 15 | -- 16 | ------------------------------------------------------------------------------ 17 | 18 | with Interfaces; 19 | 20 | package Saatana.Crypto with 21 | Pure => True, 22 | SPARK_Mode => On 23 | is 24 | 25 | type Stream_Offset is range -2 ** 62 .. 2 ** 62 - 1; -- Restrict the range to accomodate the prover. 26 | subtype Stream_Index is Stream_Offset range 0 .. 2 ** 60 - 1; 27 | subtype Stream_Count is Stream_Offset range 0 .. 2 ** 62 - 1; 28 | 29 | type Byte is new Interfaces.Unsigned_8; 30 | type Word_32 is new Interfaces.Unsigned_32; -- "Word" would be confusing on 64 bit machines. 31 | 32 | -- For type conversions. 33 | type General_Stream is array (Stream_Index range <>) of Byte; 34 | 35 | -- 36 | -- To_Unsigned 37 | -- 38 | -- Converts the maximum four first values of the given Stream to a Word_32, 39 | -- assuming Low_Order_First byte order (little endian). 40 | -- 41 | function To_Unsigned (Value : in General_Stream) return Word_32 with 42 | Global => null, 43 | Depends => (To_Unsigned'Result => (Value)), 44 | Post => (To_Unsigned'Result = (if Value'Length > 0 then (Shift_Left (Word_32 (Value (Value'First)), 0)) else 0) + 45 | (if Value'Length > 1 then (Shift_Left (Word_32 (Value (Value'First + 1)), 8)) else 0) + 46 | (if Value'Length > 2 then (Shift_Left (Word_32 (Value (Value'First + 2)), 16)) else 0) + 47 | (if Value'Length > 3 then (Shift_Left (Word_32 (Value (Value'First + 3)), 24)) else 0)); 48 | 49 | -- 50 | -- To_Stream 51 | -- 52 | -- Converts the given Word_32 value to a stream in Low_Order_First 53 | -- byte order (little endian). 54 | -- 55 | function To_Stream (Value : in Word_32) return General_Stream with 56 | Global => null, 57 | Depends => (To_Stream'Result => Value), 58 | Post => ((To_Stream'Result'Length = 4 and To_Stream'Result'First = 0) and then 59 | To_Stream'Result = (0 => Byte (Shift_Right (Value, 0) mod 256), 60 | 1 => Byte (Shift_Right (Value, 8) mod 256), 61 | 2 => Byte (Shift_Right (Value, 16) mod 256), 62 | 3 => Byte (Shift_Right (Value, 24) mod 256))); 63 | 64 | -- Provide some basic primitives. 65 | type Ciphertext_Stream is new General_Stream with Relaxed_Initialization; 66 | type Key_Stream is new General_Stream; 67 | type Plaintext_Stream is new General_Stream with Relaxed_Initialization; 68 | type MAC_Stream is new General_Stream; 69 | type Nonce_Stream is new General_Stream; 70 | 71 | function "+" (Left : in Nonce_Stream; 72 | Right : in Nonce_Stream) return Nonce_Stream with 73 | Global => null, 74 | Depends => ("+"'Result => (Left, 75 | Right)), 76 | Pre => (Right'Length = Left'Length); 77 | 78 | private 79 | 80 | -- 81 | -- To_Stream 82 | -- 83 | function To_Stream (Value : in Word_32) return General_Stream is 84 | (General_Stream' 85 | (0 => Byte (Value / 2 ** 0 mod 256), 86 | 1 => Byte (Value / 2 ** 8 mod 256), 87 | 2 => Byte (Value / 2 ** 16 mod 256), 88 | 3 => Byte (Value / 2 ** 24 mod 256))); 89 | 90 | -- 91 | -- To_Unsigned 92 | -- 93 | -- Converts the maximum four first values of the given Stream to a Word_32, 94 | -- assuming Low_Order_First byte order (little endian). 95 | -- 96 | function To_Unsigned (Value : in General_Stream) return Word_32 is 97 | ((if Value'Length > 0 then Word_32 (Value (Value'First + 0)) * 2 ** 0 else 0) + 98 | (if Value'Length > 1 then Word_32 (Value (Value'First + 1)) * 2 ** 8 else 0) + 99 | (if Value'Length > 2 then Word_32 (Value (Value'First + 2)) * 2 ** 16 else 0) + 100 | (if Value'Length > 3 then Word_32 (Value (Value'First + 3)) * 2 ** 24 else 0)); 101 | 102 | end Saatana.Crypto; 103 | -------------------------------------------------------------------------------- /src/saatana.ads: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------ 2 | -- Copyright (C) 2020 by Heisenbug Ltd. (gh+saatana@heisenbug.eu) 3 | -- 4 | -- This work is free. You can redistribute it and/or modify it under the 5 | -- terms of the Do What The Fuck You Want To Public License, Version 2, 6 | -- as published by Sam Hocevar. See the LICENSE file for more details. 7 | ------------------------------------------------------------------------------ 8 | pragma License (Unrestricted); 9 | 10 | ------------------------------------------------------------------------------ 11 | -- 12 | -- SPARK/Ada Algorithms Targeting Advanced Network Applications 13 | -- 14 | -- Saatana - Root package 15 | -- 16 | ------------------------------------------------------------------------------ 17 | 18 | package Saatana with 19 | Pure => True, 20 | SPARK_Mode => On 21 | is 22 | 23 | Version : constant String := "2.0.1-prerelease"; 24 | 25 | end Saatana; 26 | -------------------------------------------------------------------------------- /tests/phelix/test_phelix.adb: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------ 2 | -- Copyright (C) 2017-2020 by Heisenbug Ltd. (gh+saatana@heisenbug.eu) 3 | -- 4 | -- This work is free. You can redistribute it and/or modify it under the 5 | -- terms of the Do What The Fuck You Want To Public License, Version 2, 6 | -- as published by Sam Hocevar. See the LICENSE file for more details. 7 | ------------------------------------------------------------------------------ 8 | pragma License (Unrestricted); 9 | 10 | with Ada.Command_Line; 11 | with Ada.Text_IO; 12 | with Saatana.Crypto.Phelix.Test_Vectors; 13 | 14 | procedure Test_Phelix with 15 | SPARK_Mode => Off 16 | is 17 | pragma Assertion_Policy (Check); 18 | 19 | use type Saatana.Crypto.MAC_Stream; 20 | use type Saatana.Crypto.Stream_Offset; 21 | 22 | Num_Tests : Natural := 0; 23 | Num_Succeeded : Natural := 0; 24 | 25 | procedure Add_Test (Passed : in Boolean); 26 | procedure Add_Test (Passed : in Boolean) is 27 | begin 28 | Num_Tests := Num_Tests + 1; 29 | 30 | Num_Succeeded := (if Passed then Num_Succeeded + 1 else Num_Succeeded); 31 | 32 | if not Passed then 33 | Ada.Text_IO.Put_Line (" Test" & Natural'Image (Num_Tests) & " failed!"); 34 | end if; 35 | end Add_Test; 36 | 37 | function Image (S : in Saatana.Crypto.General_Stream) return String; 38 | function Image (S : in Saatana.Crypto.General_Stream) return String 39 | is 40 | type Character_Lookup is array (Saatana.Crypto.Byte range <>) of Character; 41 | Result : String (1 .. S'Length * 2); 42 | Hex_Char : constant Character_Lookup (0 .. 15) := "0123456789ABCDEF"; 43 | use type Saatana.Crypto.Byte; 44 | begin 45 | for I in S'Range loop 46 | Result (Result'First + Natural (I - S'First) * 2) := Hex_Char (S (I) / 16); 47 | Result (Result'First + Natural (I - S'First) * 2 + 1) := Hex_Char (S (I) mod 16); 48 | end loop; 49 | 50 | return Result; 51 | end Image; 52 | 53 | Verbose : Boolean := False; 54 | KAT_Only : Boolean := False; 55 | begin 56 | Evaluate_Command_Line : 57 | for Number in 1 .. Ada.Command_Line.Argument_Count loop 58 | Check_Argument : 59 | declare 60 | Argument : constant String := 61 | Ada.Command_Line.Argument (Number => Number); 62 | begin 63 | if Argument = "--verbose" then 64 | Verbose := True; 65 | elsif Argument = "--kat-only" then 66 | KAT_Only := True; 67 | else 68 | Ada.Text_IO.Put_Line 69 | (File => Ada.Text_IO.Standard_Error, 70 | Item => "Unrecognized command line parameter """ & Argument & """ ignored."); 71 | end if; 72 | end Check_Argument; 73 | end loop Evaluate_Command_Line; 74 | 75 | Ada.Text_IO.Put_Line ("Running KAT (Known Answers Tests)..."); 76 | 77 | KAT_Loop : 78 | for T of Saatana.Crypto.Phelix.Test_Vectors.KAT loop 79 | Test_Encrypt_Decrypt : 80 | declare 81 | use type Saatana.Crypto.Ciphertext_Stream; 82 | use type Saatana.Crypto.Plaintext_Stream; 83 | use type Saatana.Crypto.Phelix.MAC_Size_32; 84 | 85 | Mac_Len : constant Saatana.Crypto.Phelix.MAC_Size_32 := 8 * T.MAC.all'Length; 86 | This : Saatana.Crypto.Phelix.Context; 87 | begin 88 | Saatana.Crypto.Phelix.Setup_Key (This => This, 89 | Key => T.Key.all, 90 | Mac_Size => Mac_Len); 91 | 92 | Test_Encrypt_Decrypt_Bytes : 93 | declare 94 | Result_Cipher : Saatana.Crypto.Ciphertext_Stream (T.Plaintext.all'Range); 95 | Result_Plaintext : Saatana.Crypto.Plaintext_Stream (T.Plaintext.all'Range); 96 | Result_MAC : Saatana.Crypto.MAC_Stream (0 .. Saatana.Crypto.Stream_Offset (Mac_Len) / 8 - 1); 97 | begin 98 | Saatana.Crypto.Phelix.Setup_Nonce (This => This, 99 | Nonce => T.Nonce.all); 100 | Saatana.Crypto.Phelix.Process_AAD (This => This, 101 | Aad => T.Aad.all); 102 | Saatana.Crypto.Phelix.Encrypt_Bytes (This => This, 103 | Source => T.Plaintext.all, 104 | Destination => Result_Cipher); 105 | Saatana.Crypto.Phelix.Finalize (This => This, 106 | Mac => Result_MAC); 107 | 108 | if Verbose then 109 | -- KAT input. 110 | Ada.Text_IO.Put_Line ("Key : " & Image (Saatana.Crypto.General_Stream (T.Key.all))); 111 | Ada.Text_IO.Put_Line ("Nonce : " & Image (Saatana.Crypto.General_Stream (T.Nonce.all))); 112 | Ada.Text_IO.Put_Line ("AAD : " & Image (Saatana.Crypto.General_Stream (T.Aad.all))); 113 | Ada.Text_IO.Put_Line ("Plaintext : " & Image (Saatana.Crypto.General_Stream (T.Plaintext.all))); 114 | -- Computed output, being compared with expected from KAT vector. 115 | Ada.Text_IO.Put_Line ("Ciphertext: " & Image (Saatana.Crypto.General_Stream (Result_Cipher))); 116 | Ada.Text_IO.Put_Line ("MAC : " & Image (Saatana.Crypto.General_Stream (Result_MAC))); 117 | Ada.Text_IO.New_Line; 118 | end if; 119 | 120 | Add_Test (Result_Cipher = T.Cipher.all); 121 | Add_Test (Result_MAC = T.MAC.all); 122 | 123 | Saatana.Crypto.Phelix.Setup_Nonce (This => This, 124 | Nonce => T.Nonce.all); 125 | Saatana.Crypto.Phelix.Process_AAD (This => This, 126 | Aad => T.Aad.all); 127 | Saatana.Crypto.Phelix.Decrypt_Bytes (This => This, 128 | Source => T.Cipher.all, 129 | Destination => Result_Plaintext); 130 | Saatana.Crypto.Phelix.Finalize (This => This, 131 | Mac => Result_MAC); 132 | 133 | Add_Test (Result_Plaintext = T.Plaintext.all); 134 | Add_Test (Result_MAC = T.MAC.all); 135 | 136 | -- Same as above, but with byte-for-byte en/de-cryption. This 137 | -- is done to verify that the implementation actually works as a 138 | -- stream cipher. 139 | -- NOTE: Phelix does still require processing of 32-bit words in 140 | -- each step, so we cannot exactly call it byte-for-byte 141 | -- processing. If an actual implementation needs to encrypt 142 | -- a stream in a byte-for-byte manner, it needs to store up 143 | -- to four bytes of the plaintext or cipher stream before 144 | -- calling Encrypt/Decrypt_Bytes. 145 | Saatana.Crypto.Phelix.Setup_Nonce (This => This, 146 | Nonce => T.Nonce.all); 147 | Saatana.Crypto.Phelix.Process_AAD (This => This, 148 | Aad => T.Aad.all); 149 | 150 | Loop_Over_Plaintext_Bytes : 151 | for I in T.Plaintext.all'Range loop 152 | if I mod 4 = 0 then 153 | Process_Plaintext_Word : 154 | declare 155 | Last_Byte : constant Saatana.Crypto.Stream_Index := 156 | Saatana.Crypto.Stream_Index'Min (I + 3, 157 | T.Plaintext.all'Last); 158 | begin 159 | -- Process only on word boundaries. 160 | Saatana.Crypto.Phelix.Encrypt_Bytes 161 | (This => This, 162 | Source => T.Plaintext.all (I .. Last_Byte), 163 | Destination => Result_Cipher (I .. Last_Byte)); 164 | end Process_Plaintext_Word; 165 | end if; 166 | end loop Loop_Over_Plaintext_Bytes; 167 | 168 | Saatana.Crypto.Phelix.Finalize (This => This, 169 | Mac => Result_MAC); 170 | 171 | Add_Test (Result_Cipher = T.Cipher.all); 172 | Add_Test (Result_MAC = T.MAC.all); 173 | 174 | Saatana.Crypto.Phelix.Setup_Nonce (This => This, 175 | Nonce => T.Nonce.all); 176 | Saatana.Crypto.Phelix.Process_AAD (This => This, 177 | Aad => T.Aad.all); 178 | 179 | Loop_Over_Ciphertext_Bytes : 180 | for I in T.Cipher.all'Range loop 181 | if I mod 4 = 0 then 182 | Process_Ciphertext_Word : 183 | declare 184 | Last_Byte : constant Saatana.Crypto.Stream_Index := 185 | Saatana.Crypto.Stream_Index'Min (I + 3, 186 | T.Plaintext.all'Last); 187 | begin 188 | Saatana.Crypto.Phelix.Decrypt_Bytes 189 | (This => This, 190 | Source => T.Cipher.all (I .. Last_Byte), 191 | Destination => Result_Plaintext (I .. Last_Byte)); 192 | end Process_Ciphertext_Word; 193 | end if; 194 | end loop Loop_Over_Ciphertext_Bytes; 195 | 196 | Saatana.Crypto.Phelix.Finalize (This => This, 197 | Mac => Result_MAC); 198 | 199 | Add_Test (Result_Plaintext = T.Plaintext.all); 200 | Add_Test (Result_MAC = T.MAC.all); 201 | 202 | end Test_Encrypt_Decrypt_Bytes; 203 | 204 | Test_Encrypt_Decrypt_Packets : 205 | declare 206 | Enc_Packet : Saatana.Crypto.Ciphertext_Stream (0 .. T.Plaintext.all'Length + T.Aad.all'Length - 1); 207 | Dec_Packet : Saatana.Crypto.Plaintext_Stream (0 .. T.Plaintext.all'Length + T.Aad.all'Length - 1); 208 | Result_MAC : Saatana.Crypto.MAC_Stream (0 .. Saatana.Crypto.Stream_Offset (Mac_Len) / 8 - 1); 209 | begin 210 | Saatana.Crypto.Phelix.Encrypt_Packet (This => This, 211 | Nonce => T.Nonce.all, 212 | Header => T.Aad.all, 213 | Payload => T.Plaintext.all, 214 | Packet => Enc_Packet, 215 | Mac => Result_MAC); 216 | Add_Test (Saatana.Crypto.Ciphertext_Stream (T.Aad.all) & T.Cipher.all = Enc_Packet); 217 | Add_Test (Result_MAC = T.MAC.all); 218 | 219 | Saatana.Crypto.Phelix.Decrypt_Packet (This => This, 220 | Nonce => T.Nonce.all, 221 | Header => T.Aad.all, 222 | Payload => T.Cipher.all, 223 | Packet => Dec_Packet, 224 | Mac => Result_MAC); 225 | Add_Test (T.Aad.all & T.Plaintext.all = Dec_Packet); 226 | Add_Test (Result_MAC = T.MAC.all); 227 | end Test_Encrypt_Decrypt_Packets; 228 | end Test_Encrypt_Decrypt; 229 | end loop KAT_Loop; 230 | 231 | if not KAT_Only then 232 | Ada.Text_IO.Put_Line ("Running additional ""Transmit"" tests..."); 233 | 234 | Transmit_Tests : 235 | declare 236 | KEY_LENGTH : constant := Saatana.Crypto.Phelix.Max_Key_Size / 8; 237 | MAC_LENGTH : constant := Saatana.Crypto.Phelix.Max_MAC_Size / 8; 238 | NONCE_LENGTH : constant := Saatana.Crypto.Phelix.Max_Nonce_Size / 8; 239 | 240 | NONCE_FIRST : constant Saatana.Crypto.Stream_Offset := 0; 241 | NONCE_LAST : constant Saatana.Crypto.Stream_Offset := NONCE_FIRST + NONCE_LENGTH - 1; 242 | MAC_FIRST : constant Saatana.Crypto.Stream_Offset := NONCE_LAST + 1; 243 | MAC_LAST : constant Saatana.Crypto.Stream_Offset := MAC_FIRST + MAC_LENGTH - 1; 244 | EMPTY : constant Saatana.Crypto.General_Stream := (1 .. 0 => 0); 245 | 246 | use type Saatana.Crypto.Byte; 247 | use type Saatana.Crypto.Ciphertext_Stream; 248 | use type Saatana.Crypto.Key_Stream; 249 | use type Saatana.Crypto.Nonce_Stream; 250 | use type Saatana.Crypto.Phelix.MAC_Size_32; 251 | 252 | procedure Send_Nonce (Key : in Saatana.Crypto.Key_Stream; 253 | Nonce : in Saatana.Crypto.Nonce_Stream; 254 | Packet : out Saatana.Crypto.Ciphertext_Stream); 255 | procedure Receive_Nonce (Key : in Saatana.Crypto.Key_Stream; 256 | Packet : in Saatana.Crypto.Ciphertext_Stream; 257 | Authenticated : out Boolean); 258 | 259 | procedure Receive_Nonce (Key : in Saatana.Crypto.Key_Stream; 260 | Packet : in Saatana.Crypto.Ciphertext_Stream; 261 | Authenticated : out Boolean) 262 | is 263 | Ctx : Saatana.Crypto.Phelix.Context; 264 | Mac : Saatana.Crypto.MAC_Stream (0 .. MAC_LENGTH - 1); 265 | Nonce : constant Saatana.Crypto.Nonce_Stream := 266 | Saatana.Crypto.Nonce_Stream (Packet (NONCE_FIRST .. NONCE_LAST)); 267 | Received_Nonce : Saatana.Crypto.Nonce_Stream (0 .. NONCE_LENGTH - 1); 268 | begin 269 | Saatana.Crypto.Phelix.Setup_Key (This => Ctx, 270 | Key => Key, 271 | Mac_Size => MAC_LENGTH * 8); 272 | -- Verify received packet. 273 | Saatana.Crypto.Phelix.Decrypt_Packet 274 | (This => Ctx, 275 | Nonce => Nonce, 276 | Header => Saatana.Crypto.Plaintext_Stream (Nonce), 277 | Payload => Saatana.Crypto.Ciphertext_Stream (EMPTY), 278 | Packet => Saatana.Crypto.Plaintext_Stream (Received_Nonce), 279 | Mac => Mac); 280 | -- MAC must match, otherwise someone tampered with our packet. 281 | Authenticated := 282 | Mac = Saatana.Crypto.MAC_Stream (Packet (MAC_FIRST .. MAC_LAST)); 283 | end Receive_Nonce; 284 | 285 | procedure Send_Nonce (Key : in Saatana.Crypto.Key_Stream; 286 | Nonce : in Saatana.Crypto.Nonce_Stream; 287 | Packet : out Saatana.Crypto.Ciphertext_Stream) is 288 | Ctx : Saatana.Crypto.Phelix.Context; 289 | begin 290 | Saatana.Crypto.Phelix.Setup_Key (This => Ctx, 291 | Key => Key, 292 | Mac_Size => MAC_LENGTH * 8); 293 | -- Transmit that Nonce to the client. 294 | Saatana.Crypto.Phelix.Encrypt_Packet 295 | (This => Ctx, 296 | Nonce => Nonce, 297 | Header => Saatana.Crypto.Plaintext_Stream (Nonce), 298 | Payload => Saatana.Crypto.Plaintext_Stream (EMPTY), 299 | Packet => Packet (NONCE_FIRST .. NONCE_LAST), 300 | Mac => Saatana.Crypto.MAC_Stream (Packet (MAC_FIRST .. MAC_LAST))); 301 | end Send_Nonce; 302 | 303 | Key : Saatana.Crypto.Key_Stream (0 .. KEY_LENGTH - 1); 304 | Nonce : Saatana.Crypto.Nonce_Stream (0 .. NONCE_LENGTH - 1); 305 | Packet : Saatana.Crypto.Ciphertext_Stream (NONCE_FIRST .. MAC_LAST); 306 | Authenticated : Boolean; 307 | begin 308 | -- Communication setup. 309 | Key := (others => 0); 310 | -- Generate random/unique nonce. 311 | Nonce := (others => 0); 312 | 313 | Check_MAC_Handling : 314 | for I in 1 .. 1024 loop 315 | Send_Nonce (Key => Key, 316 | Nonce => Nonce, 317 | Packet => Packet); 318 | 319 | if Verbose then 320 | Ada.Text_IO.Put_Line ("Key : " & Image (Saatana.Crypto.General_Stream (Key))); 321 | Ada.Text_IO.Put_Line ("Nonce : " & Image (Saatana.Crypto.General_Stream (Nonce))); 322 | Ada.Text_IO.Put_Line ("Packet: " & Image (Saatana.Crypto.General_Stream (Packet))); 323 | Ada.Text_IO.New_Line; 324 | end if; 325 | 326 | -- Check if the MAC matches with whatever has been sent. 327 | Receive_Nonce (Key => Key, 328 | Packet => Packet, 329 | Authenticated => Authenticated); 330 | Add_Test (Authenticated); 331 | 332 | -- Tamper with the packet. Authentication should fail now. 333 | Receive_Nonce 334 | (Key => Key, 335 | Packet => (Packet (Packet'First) xor 1) & Packet (Packet'First + 1 .. Packet'Last), 336 | Authenticated => Authenticated); 337 | Add_Test (not Authenticated); 338 | 339 | -- Use Nonce and MAC as new Key and increment the nonce. 340 | Key := Saatana.Crypto.Key_Stream (Packet (MAC_FIRST .. MAC_LAST)) & Key (NONCE_FIRST .. NONCE_LAST); 341 | 342 | -- "Randomly" advance Nonce. 343 | -- 344 | -- We take the current key stream, null out its two low order bytes, 345 | -- and the result is being added Key (0) * Key (1) times to Nonce to 346 | -- create a new seemingly random Nonce. 347 | Shuffle_Nonce : 348 | declare 349 | Operand : constant Saatana.Crypto.Nonce_Stream := 350 | Saatana.Crypto.Nonce_Stream (Key (Key'First .. Key'First + 13) & (14 .. NONCE_LENGTH - 1 => 0)); 351 | begin 352 | Loop_For_First_Byte_Of_Key : 353 | for J in 1 .. Key (Key'First) loop 354 | Loop_For_Second_Byte_Of_Key : 355 | for K in 1 .. Key (Key'First + 1) loop 356 | Nonce := Nonce + Operand; 357 | end loop Loop_For_Second_Byte_Of_Key; 358 | end loop Loop_For_First_Byte_Of_Key; 359 | end Shuffle_Nonce; 360 | end loop Check_MAC_Handling; 361 | 362 | -- Check for any regressions. If, after all this Nonce shuffling above, 363 | -- the last packet is still the expected one, we are good. 364 | Add_Test (Image (Saatana.Crypto.General_Stream (Packet)) = 365 | "8F636CA7CC87C0AC88B35964B605E0005A586B0519BA64C6245C8724D3BEDAF1"); 366 | end Transmit_Tests; 367 | end if; -- KAT only requested 368 | 369 | Ada.Text_IO.Put_Line ("Test results:" 370 | & Natural'Image (Num_Succeeded) 371 | & " out of" & Natural'Image (Num_Tests) 372 | & " succeeded."); 373 | Ada.Text_IO.Put_Line (if Num_Tests = Num_Succeeded then "" else ""); 374 | Ada.Command_Line.Set_Exit_Status (Code => (if Num_Tests = Num_Succeeded 375 | then Ada.Command_Line.Success 376 | else Ada.Command_Line.Failure)); 377 | end Test_Phelix; 378 | -------------------------------------------------------------------------------- /tests/phelix/test_phelix_api.adb: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------ 2 | -- Copyright (C) 2020 by Heisenbug Ltd. (gh+saatana@heisenbug.eu) 3 | -- 4 | -- This work is free. You can redistribute it and/or modify it under the 5 | -- terms of the Do What The Fuck You Want To Public License, Version 2, 6 | -- as published by Sam Hocevar. See the LICENSE file for more details. 7 | ------------------------------------------------------------------------------ 8 | pragma License (Unrestricted); 9 | 10 | with Ada.Command_Line; 11 | with Saatana.Crypto.Phelix; 12 | 13 | ------------------------------------------------------------------------------ 14 | -- 15 | -- SPARK/Ada Algorithms Targeting Advanced Network Applications 16 | -- 17 | -- Saatana - API demonstration. 18 | -- 19 | -- Intended to show the API and how to use it, and proof of correctness 20 | -- regarding call sequence. 21 | -- 22 | -- Please note that we re-use the Nonce here purely for demonstration purposes! 23 | -- 24 | ------------------------------------------------------------------------------ 25 | 26 | procedure Test_Phelix_API with 27 | SPARK_Mode => On 28 | is 29 | pragma Assertion_Policy (Check); 30 | 31 | use type Saatana.Crypto.Stream_Index; 32 | use type Saatana.Crypto.Word_32; 33 | 34 | Key : constant Saatana.Crypto.Key_Stream := (0 .. 15 => 1); 35 | Nonce : constant Saatana.Crypto.Nonce_Stream := (0 .. 15 => 0); 36 | AAD : constant Saatana.Crypto.Plaintext_Stream 37 | := (0 => Character'Pos ('A'), 38 | 1 => Character'Pos ('A'), 39 | 2 => Character'Pos ('D')); 40 | Plaintext : constant Saatana.Crypto.Plaintext_Stream 41 | := (0 => Character'Pos ('P'), 42 | 1 => Character'Pos ('l'), 43 | 2 => Character'Pos ('a'), 44 | 3 => Character'Pos ('i'), 45 | 4 => Character'Pos ('n'), 46 | 5 => Character'Pos ('t'), 47 | 6 => Character'Pos ('e'), 48 | 7 => Character'Pos ('x'), 49 | 8 => Character'Pos ('t')); 50 | 51 | subtype Packet_Range is Saatana.Crypto.Stream_Index range 52 | 0 .. AAD'Length + Plaintext'Length - 1; 53 | subtype AAD_Range is Packet_Range range 54 | Packet_Range'First .. AAD'Length - 1; 55 | subtype PT_Range is Packet_Range range 56 | AAD'Length .. Packet_Range'Last; 57 | 58 | Packet_Enc : Saatana.Crypto.Ciphertext_Stream (Packet_Range) := (others => 0); 59 | Packet_Dec : Saatana.Crypto.Plaintext_Stream (Packet_Range) := (others => 0); 60 | MAC : Saatana.Crypto.MAC_Stream (0 .. 7); 61 | This : Saatana.Crypto.Phelix.Context; 62 | begin 63 | -- 64 | -- Standard API. 65 | -- 66 | 67 | -- 68 | -- Encryption 69 | -- 70 | -- Set up key, add a nonce, process AAD (if any), encrypt the payload and 71 | -- finalize for MAC calculation. 72 | Saatana.Crypto.Phelix.Setup_Key (This => This, 73 | Key => Key, 74 | Mac_Size => MAC'Length * 8); 75 | Saatana.Crypto.Phelix.Setup_Nonce (This => This, 76 | Nonce => Nonce); 77 | Saatana.Crypto.Phelix.Process_AAD (This => This, 78 | Aad => AAD); 79 | Packet_Enc (AAD_Range) := Saatana.Crypto.Ciphertext_Stream (AAD); 80 | Saatana.Crypto.Phelix.Encrypt_Bytes 81 | (This => This, 82 | Source => Plaintext, 83 | Destination => Packet_Enc (PT_Range)); 84 | Saatana.Crypto.Phelix.Finalize (This => This, 85 | Mac => MAC); 86 | 87 | -- 88 | -- Decryption 89 | -- 90 | -- Set up key, add a nonce, process AAD (if any), decrypt the payload and 91 | -- finalize for MAC calculation. 92 | Saatana.Crypto.Phelix.Setup_Key (This => This, 93 | Key => Key, 94 | Mac_Size => MAC'Length * 8); 95 | Saatana.Crypto.Phelix.Setup_Nonce (This => This, 96 | Nonce => Nonce); 97 | Packet_Dec (AAD_Range) := 98 | Saatana.Crypto.Plaintext_Stream (Packet_Enc (AAD_Range)); 99 | Saatana.Crypto.Phelix.Process_AAD (This => This, 100 | Aad => Packet_Dec (AAD_Range)); 101 | Saatana.Crypto.Phelix.Decrypt_Bytes 102 | (This => This, 103 | Source => Packet_Enc (PT_Range), 104 | Destination => Packet_Dec (PT_Range)); 105 | Saatana.Crypto.Phelix.Finalize (This => This, 106 | Mac => MAC); 107 | 108 | -- 109 | -- Extended API. 110 | -- Does most of the above in a single step. 111 | -- 112 | 113 | -- 114 | -- Encryption 115 | -- 116 | Saatana.Crypto.Phelix.Setup_Key (This => This, 117 | Key => Key, 118 | Mac_Size => MAC'Length * 8); 119 | Saatana.Crypto.Phelix.Encrypt_Packet (This => This, 120 | Nonce => Nonce, 121 | Header => AAD, 122 | Payload => Plaintext, 123 | Packet => Packet_Enc, 124 | Mac => MAC); 125 | 126 | -- 127 | -- Decryption 128 | -- 129 | Saatana.Crypto.Phelix.Setup_Key (This => This, 130 | Key => Key, 131 | Mac_Size => MAC'Length * 8); 132 | Saatana.Crypto.Phelix.Decrypt_Packet 133 | (This => This, 134 | Nonce => Nonce, 135 | Header => Saatana.Crypto.Plaintext_Stream (Packet_Enc (AAD_Range)), 136 | Payload => Packet_Enc (PT_Range), 137 | Packet => Packet_Dec, 138 | Mac => MAC); 139 | 140 | Set_Exit_Status : 141 | declare 142 | use type Saatana.Crypto.Plaintext_Stream; 143 | Exit_Status : constant Ada.Command_Line.Exit_Status 144 | := (if Packet_Dec = AAD & Plaintext 145 | then Ada.Command_Line.Success 146 | else Ada.Command_Line.Failure); 147 | begin 148 | pragma Warnings 149 | (Off, 150 | "no Global contract available for ""Set_Exit_Status"""); 151 | -- Setting the exit status is not part of the SPARK verification. This 152 | -- program is not even intended to be executed, the whole purpose is to 153 | -- verify that SPARK can successfully prove the API usage. 154 | Ada.Command_Line.Set_Exit_Status (Exit_Status); 155 | pragma Warnings (On, 156 | "no Global contract available for ""Set_Exit_Status"""); 157 | end Set_Exit_Status; 158 | end Test_Phelix_API; 159 | -------------------------------------------------------------------------------- /tests/saatana-crypto-stream_tools.adb: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------ 2 | -- Copyright (C) 2017-2020 by Heisenbug Ltd. (gh+saatana@heisenbug.eu) 3 | -- 4 | -- This work is free. You can redistribute it and/or modify it under the 5 | -- terms of the Do What The Fuck You Want To Public License, Version 2, 6 | -- as published by Sam Hocevar. See the LICENSE file for more details. 7 | ------------------------------------------------------------------------------ 8 | pragma License (Unrestricted); 9 | 10 | package body Saatana.Crypto.Stream_Tools with 11 | SPARK_Mode => Off 12 | is 13 | 14 | function To_Stream (Value : in String) return General_Stream; 15 | function To_Stream (Value : in String) return General_Stream is 16 | Result : General_Stream (0 .. Value'Length / 2 - 1); 17 | begin 18 | for I in Result'Range loop 19 | Convert_Hex_Byte : 20 | declare 21 | Str_Idx : constant Positive := Value'First + Natural (I) * 2; 22 | begin 23 | Result (I) := Byte'Value ("16#" & Value (Str_Idx .. Str_Idx + 1) & "#"); 24 | end Convert_Hex_Byte; 25 | end loop; 26 | 27 | return Result; 28 | end To_Stream; 29 | 30 | function To_Stream (Value : in String) return Ciphertext_Stream_Access is 31 | begin 32 | return new Ciphertext_Stream'(Ciphertext_Stream (General_Stream'(To_Stream (Value)))); 33 | end To_Stream; 34 | 35 | function To_Stream (Value : in String) return Key_Stream_Access is 36 | begin 37 | return new Key_Stream'(Key_Stream (General_Stream'(To_Stream (Value)))); 38 | end To_Stream; 39 | 40 | function To_Stream (Value : in String) return MAC_Stream_Access is 41 | begin 42 | return new MAC_Stream'(MAC_Stream (General_Stream'(To_Stream (Value)))); 43 | end To_Stream; 44 | 45 | function To_Stream (Value : in String) return Nonce_Stream_Access is 46 | begin 47 | return new Nonce_Stream'(Nonce_Stream (General_Stream'(To_Stream (Value)))); 48 | end To_Stream; 49 | 50 | function To_Stream (Value : in String) return Plaintext_Stream_Access is 51 | begin 52 | return new Plaintext_Stream'(Plaintext_Stream (General_Stream'(To_Stream (Value)))); 53 | end To_Stream; 54 | 55 | end Saatana.Crypto.Stream_Tools; 56 | -------------------------------------------------------------------------------- /tests/saatana-crypto-stream_tools.ads: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------ 2 | -- Copyright (C) 2017-2020 by Heisenbug Ltd. (gh+saatana@heisenbug.eu) 3 | -- 4 | -- This work is free. You can redistribute it and/or modify it under the 5 | -- terms of the Do What The Fuck You Want To Public License, Version 2, 6 | -- as published by Sam Hocevar. See the LICENSE file for more details. 7 | ------------------------------------------------------------------------------ 8 | pragma License (Unrestricted); 9 | 10 | package Saatana.Crypto.Stream_Tools with 11 | SPARK_Mode => Off 12 | is 13 | 14 | type Ciphertext_Stream_Access is not null access constant Ciphertext_Stream; 15 | type Key_Stream_Access is not null access constant Key_Stream; 16 | type MAC_Stream_Access is not null access constant MAC_Stream; 17 | type Nonce_Stream_Access is not null access constant Nonce_Stream; 18 | type Plaintext_Stream_Access is not null access constant Plaintext_Stream; 19 | 20 | function To_Stream (Value : in String) return Ciphertext_Stream_Access; 21 | function To_Stream (Value : in String) return Key_Stream_Access; 22 | function To_Stream (Value : in String) return MAC_Stream_Access; 23 | function To_Stream (Value : in String) return Nonce_Stream_Access; 24 | function To_Stream (Value : in String) return Plaintext_Stream_Access; 25 | 26 | end Saatana.Crypto.Stream_Tools; 27 | --------------------------------------------------------------------------------