diff --git a/.github/workflows/dh.yml b/.github/workflows/dh.yml new file mode 100644 index 0000000..2b4610c --- /dev/null +++ b/.github/workflows/dh.yml @@ -0,0 +1,74 @@ +name: Verification of DH Case Study + +on: + push: + +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 10 + container: + image: ghcr.io/viperproject/gobra@sha256:0e7419455a3648d006e8a9957380e94c1a8e52d2d4b1ce2425af852dc275fb29 # Gobra commit f21fe70 + steps: + - name: Install prerequisites + run: apt-get update && apt-get install -y git wget jq + + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Install Go + run: | + mkdir tmp + cd tmp + wget --quiet https://go.dev/dl/go1.17.3.linux-amd64.tar.gz + rm -rf /usr/local/go && tar -C /usr/local -xzf go1.17.3.linux-amd64.tar.gz + cd ../ + rm -rf tmp + # add go to path + echo "/usr/local/go/bin" >> $GITHUB_PATH + + - name: Point Go to repo copy + run: go mod edit -replace github.com/viperproject/ReusableProtocolVerificationLibrary=$GITHUB_WORKSPACE/ReusableVerificationLibrary + working-directory: casestudies/dh + + - name: Compile DH initiator & responder + run: ./compile.sh + working-directory: casestudies/dh + + - name: Execute DH initiator & responder + run: ./dh + working-directory: casestudies/dh + + - name: Setup verification of DH + run: | + ./load-modules.sh "$GITHUB_WORKSPACE/.modules" + mkdir -p $GITHUB_WORKSPACE/.modules/github.com/viperproject + working-directory: casestudies/dh + + - name: Link Reusable Verification Library + run: ln -s $GITHUB_WORKSPACE/ReusableVerificationLibrary .modules/github.com/viperproject/ReusableProtocolVerificationLibrary + + - name: Verify DH initiator + run: | + mkdir -p .gobra + gobraJar="/gobra/gobra.jar" + additionalGobraArgs="-I $GITHUB_WORKSPACE/casestudies/dh/.verification/precedence -I $GITHUB_WORKSPACE/.modules --module github.com/viperproject/ProtocolVerificationCaseStudies/dh --parallelizeBranches" + java -Xss128m -jar $gobraJar --recursive -I ./ $additionalGobraArgs --includePackages initiator + working-directory: casestudies/dh + + - name: Verify DH responder role + run: | + mkdir -p .gobra + gobraJar="/gobra/gobra.jar" + additionalGobraArgs="-I $GITHUB_WORKSPACE/casestudies/dh/.verification/precedence -I $GITHUB_WORKSPACE/.modules --module github.com/viperproject/ProtocolVerificationCaseStudies/dh --parallelizeBranches" + java -Xss128m -jar $gobraJar --recursive -I ./ $additionalGobraArgs --includePackages responder + working-directory: casestudies/dh + + - name: Verify DH trace invariants & main package + run: | + mkdir -p .gobra + gobraJar="/gobra/gobra.jar" + additionalGobraArgs="-I $GITHUB_WORKSPACE/casestudies/dh/.verification/precedence -I $GITHUB_WORKSPACE/.modules --module github.com/viperproject/ProtocolVerificationCaseStudies/dh --parallelizeBranches" + # verify packages located in the current directory and in `common`: + java -Xss128m -jar $gobraJar --directory ./ common -I ./ $additionalGobraArgs + working-directory: casestudies/dh diff --git a/.github/workflows/library.yml b/.github/workflows/library.yml new file mode 100644 index 0000000..d5348cc --- /dev/null +++ b/.github/workflows/library.yml @@ -0,0 +1,21 @@ +name: Verification of Reusable Verification Library for Gobra + +on: + push: + +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 10 + container: + image: ghcr.io/viperproject/gobra@sha256:0e7419455a3648d006e8a9957380e94c1a8e52d2d4b1ce2425af852dc275fb29 # Gobra commit f21fe70 + steps: + - name: Install git + run: apt-get update && apt-get install -y git + + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Verify the entire Reusable Verification Library + run: ./verify.sh + working-directory: ReusableVerificationLibrary diff --git a/.github/workflows/nsl.yml b/.github/workflows/nsl.yml new file mode 100644 index 0000000..a4655d2 --- /dev/null +++ b/.github/workflows/nsl.yml @@ -0,0 +1,82 @@ +name: Verification of NSL Case Study + +on: + push: + +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 10 + container: + image: ghcr.io/viperproject/gobra@sha256:0e7419455a3648d006e8a9957380e94c1a8e52d2d4b1ce2425af852dc275fb29 # Gobra commit f21fe70 + steps: + - name: Install prerequisites + run: apt-get update && apt-get install -y git wget jq + + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Install Go + run: | + mkdir tmp + cd tmp + wget --quiet https://go.dev/dl/go1.17.3.linux-amd64.tar.gz + rm -rf /usr/local/go && tar -C /usr/local -xzf go1.17.3.linux-amd64.tar.gz + cd ../ + rm -rf tmp + # add go to path + echo "/usr/local/go/bin" >> $GITHUB_PATH + + - name: Point Go to repo copy + run: go mod edit -replace github.com/viperproject/ReusableProtocolVerificationLibrary=$GITHUB_WORKSPACE/ReusableVerificationLibrary + working-directory: casestudies/nsl + + - name: Compile NSL initiator & responder + run: ./compile.sh + working-directory: casestudies/nsl + + - name: Execute NSL initiator & responder + run: ./nsl + working-directory: casestudies/nsl + + - name: Setup verification of NSL + run: | + ./load-modules.sh "$GITHUB_WORKSPACE/.modules" + mkdir -p $GITHUB_WORKSPACE/.modules/github.com/viperproject + working-directory: casestudies/nsl + + - name: Link Reusable Verification Library + run: ln -s $GITHUB_WORKSPACE/ReusableVerificationLibrary .modules/github.com/viperproject/ReusableProtocolVerificationLibrary + + - name: Verify NSL initiator role 1 + run: | + mkdir -p .gobra + gobraJar="/gobra/gobra.jar" + additionalGobraArgs="-I $GITHUB_WORKSPACE/casestudies/nsl/.verification/precedence -I $GITHUB_WORKSPACE/.modules --module github.com/viperproject/ProtocolVerificationCaseStudies/nsl --parallelizeBranches" + java -Xss128m -jar $gobraJar --recursive -I ./ $additionalGobraArgs --includePackages initiator + working-directory: casestudies/nsl + + - name: Verify NSL initiator role 2 + run: | + mkdir -p .gobra + gobraJar="/gobra/gobra.jar" + additionalGobraArgs="-I $GITHUB_WORKSPACE/casestudies/nsl/.verification/precedence -I $GITHUB_WORKSPACE/.modules --module github.com/viperproject/ProtocolVerificationCaseStudies/nsl --parallelizeBranches" + java -Xss128m -jar $gobraJar --recursive -I ./ $additionalGobraArgs --includePackages initiator2 + working-directory: casestudies/nsl + + - name: Verify NSL responder role + run: | + mkdir -p .gobra + gobraJar="/gobra/gobra.jar" + additionalGobraArgs="-I $GITHUB_WORKSPACE/casestudies/nsl/.verification/precedence -I $GITHUB_WORKSPACE/.modules --module github.com/viperproject/ProtocolVerificationCaseStudies/nsl --parallelizeBranches" + java -Xss128m -jar $gobraJar --recursive -I ./ $additionalGobraArgs --includePackages responder + working-directory: casestudies/nsl + + - name: Verify NSL trace invariants & main package + run: | + mkdir -p .gobra + gobraJar="/gobra/gobra.jar" + additionalGobraArgs="-I $GITHUB_WORKSPACE/casestudies/nsl/.verification/precedence -I $GITHUB_WORKSPACE/.modules --module github.com/viperproject/ProtocolVerificationCaseStudies/nsl --parallelizeBranches" + # verify packages located in the current directory and in `common`: + java -Xss128m -jar $gobraJar --directory ./ common -I ./ $additionalGobraArgs + working-directory: casestudies/nsl diff --git a/.github/workflows/verifast-library.yml b/.github/workflows/verifast-library.yml new file mode 100644 index 0000000..441d400 --- /dev/null +++ b/.github/workflows/verifast-library.yml @@ -0,0 +1,29 @@ +name: Verification of Reusable Verification Library in C + +on: + push: + +jobs: + build-verify: + runs-on: ubuntu-latest + timeout-minutes: 15 + env: + VERIFAST_VERSION: 21.04 + steps: + - name: Install prerequisites + run: sudo apt-get update && sudo apt-get install -y git curl tar libgomp1 + + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Download and Setup VeriFast + run: | + curl -q -s -S -L --create-dirs -o VeriFast.zip https://github.com/verifast/verifast/releases/download/${{ env.VERIFAST_VERSION }}/verifast-${{ env.VERIFAST_VERSION }}-linux.tar.gz + tar xzf VeriFast.zip + # this creates a folder called 'verifast-${{ env.VERIFAST_VERSION }}' + VERIFAST_BIN="$PWD/verifast-${{ env.VERIFAST_VERSION }}/bin" + echo "$VERIFAST_BIN" >> $GITHUB_PATH + + - name: Verify Reusable Verification Library in VeriFast + run: ./verify.sh + working-directory: VeriFastPrototype/reusable_verification_library diff --git a/.github/workflows/verifast-nsl.yml b/.github/workflows/verifast-nsl.yml new file mode 100644 index 0000000..f2d4079 --- /dev/null +++ b/.github/workflows/verifast-nsl.yml @@ -0,0 +1,33 @@ +name: Verification of the NSL case study in C + +on: + push: + +jobs: + build-verify: + runs-on: ubuntu-latest + timeout-minutes: 15 + env: + VERIFAST_VERSION: 21.04 + steps: + - name: Install prerequisites + run: sudo apt-get update && sudo apt-get install -y git curl tar libgomp1 + + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Download and Setup VeriFast + run: | + curl -q -s -S -L --create-dirs -o VeriFast.zip https://github.com/verifast/verifast/releases/download/${{ env.VERIFAST_VERSION }}/verifast-${{ env.VERIFAST_VERSION }}-linux.tar.gz + tar xzf VeriFast.zip + # this creates a folder called 'verifast-${{ env.VERIFAST_VERSION }}' + VERIFAST_BIN="$PWD/verifast-${{ env.VERIFAST_VERSION }}/bin" + echo "$VERIFAST_BIN" >> $GITHUB_PATH + + - name: Compile and execute NSL initiator & responder + run: ./execute.sh + working-directory: VeriFastPrototype/nsl + + - name: Verify NSL initiator & responder using VeriFast + run: ./verify.sh + working-directory: VeriFastPrototype/nsl diff --git a/.github/workflows/wireguard.yml b/.github/workflows/wireguard.yml new file mode 100644 index 0000000..71c9fe0 --- /dev/null +++ b/.github/workflows/wireguard.yml @@ -0,0 +1,63 @@ +name: Verification of WireGuard Case Study + +on: + push: + +jobs: + build-verify: + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + IMAGE_NAME: securityprotocolimplementations-wireguard + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Create Image ID + run: | + REPO_OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') + echo "IMAGE_ID=ghcr.io/$REPO_OWNER/${{ env.IMAGE_NAME }}" >>$GITHUB_ENV + + - name: Image version + run: | + VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') + [ "$VERSION" == "main" ] && VERSION=latest + echo "IMAGE_TAG=${{ env.IMAGE_ID }}:$VERSION" >> $GITHUB_ENV + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Build image + uses: docker/build-push-action@v4 + with: + context: . + load: true + file: casestudies/wireguard/docker/Dockerfile + tags: ${{ env.IMAGE_TAG }} + labels: "runnumber=${{ github.run_id }}" + push: false + cache-from: type=gha, scope=${{ github.workflow }} + cache-to: type=gha, scope=${{ github.workflow }} + + - name: Execute initiator & responder + run: docker run ${{ env.IMAGE_TAG }} ./test.sh + + - name: Verify initiator & responder + run: docker run ${{ env.IMAGE_TAG }} ./verify.sh + + - name: Login to Github Packages + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push image + uses: docker/build-push-action@v4 + with: + context: . + file: casestudies/wireguard/docker/Dockerfile + tags: ${{ env.IMAGE_TAG }} + labels: "runnumber=${{ github.run_id }}" + push: true + cache-from: type=gha, scope=${{ github.workflow }} + cache-to: type=gha, scope=${{ github.workflow }} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a612ad9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md new file mode 100644 index 0000000..01d98ee --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# A Generic Methodology for the Modular Verification of Security Protocol Implementations + +[![Reusable Verification Library](https://github.com/viperproject/SecurityProtocolImplementations/actions/workflows/library.yml/badge.svg?branch=main)](https://github.com/viperproject/SecurityProtocolImplementations/actions/workflows/library.yml?query=branch%3Amain) +[![Reusable Verification Library for VeriFast](https://github.com/viperproject/SecurityProtocolImplementations/actions/workflows/verifast-library.yml/badge.svg?branch=main)](https://github.com/viperproject/SecurityProtocolImplementations/actions/workflows/verifast-library.yml?query=branch%3Amain) +[![NSL Case Study](https://github.com/viperproject/SecurityProtocolImplementations/actions/workflows/nsl.yml/badge.svg?branch=main)](https://github.com/viperproject/SecurityProtocolImplementations/actions/workflows/nsl.yml?query=branch%3Amain) +[![DH Case Study](https://github.com/viperproject/SecurityProtocolImplementations/actions/workflows/dh.yml/badge.svg?branch=main)](https://github.com/viperproject/SecurityProtocolImplementations/actions/workflows/dh.yml?query=branch%3Amain) +[![WireGuard Case Study](https://github.com/viperproject/SecurityProtocolImplementations/actions/workflows/wireguard.yml/badge.svg?branch=main)](https://github.com/viperproject/SecurityProtocolImplementations/actions/workflows/wireguard.yml?query=branch%3Amain) +[![NSL Case Study for VeriFast](https://github.com/viperproject/SecurityProtocolImplementations/actions/workflows/verifast-nsl.yml/badge.svg?branch=main)](https://github.com/viperproject/SecurityProtocolImplementations/actions/workflows/verifast-nsl.yml?query=branch%3Amain) + + +## Structure +- `ReusableVerificationLibrary` contains the Gobra sources that constitute the Reusable Verification Library +- `casestudies/nsl` contains the sources of the NSL case study. The implementation consists of separate packages for the initiator, initiator2 (using a slightly different code structure), and responder role. The main package sets up communication via Go channels and executes the two protocol roles as threads (using Goroutines) +- `casestudies/dh` contains the sources of the DH case study, which follows a similar structure as the NSL case study and consists of packages for the initiator and responder roles. +- `casestudies/wireguard` contains the sources of the WireGuard case study. The role implementations are mainly located in the `initiator` and `responder` packages. The trace invariants are defined in `verification/common` and `verification/labellemma` contains the WireGuard-specific lemmas and their proofs. +- `VeriFastPrototype` contains the C sources of our prototype for VeriFast. The prototype demonstrates that (1) a concurrent data structure can be built on top of VeriFast's built-in mutex offering the same local snapshots as our Reusable Verification Library, (2) a parameterized trace invariant in the form of a separation logic predicate can be defined, and (3) several lemmas, such as the uniqueness of events or the secrecy lemma, can be proven using the trace invariant. Furthermore, implementations in C of the NSL initiator & responder demonstrate that we can instantiate and use the Reusable Verification Library for VeriFast to prove secrecy and injective agreement, i.e., the same security properties as for the implementation in Go using Gobra. diff --git a/ReusableVerificationLibrary/.verification/crypto/rand/rand.gobra b/ReusableVerificationLibrary/.verification/crypto/rand/rand.gobra new file mode 100644 index 0000000..07f6a85 --- /dev/null +++ b/ReusableVerificationLibrary/.verification/crypto/rand/rand.gobra @@ -0,0 +1,4 @@ +package rand + +// empty but necessary since Gobra needs all transitively imported packages +// to perform the global variable analysis diff --git a/ReusableVerificationLibrary/.verification/crypto/rsa/rsa.gobra b/ReusableVerificationLibrary/.verification/crypto/rsa/rsa.gobra new file mode 100644 index 0000000..e947330 --- /dev/null +++ b/ReusableVerificationLibrary/.verification/crypto/rsa/rsa.gobra @@ -0,0 +1,4 @@ +package rsa + +// empty but necessary since Gobra needs all transitively imported packages +// to perform the global variable analysis diff --git a/ReusableVerificationLibrary/.verification/crypto/sha256/sha256.gobra b/ReusableVerificationLibrary/.verification/crypto/sha256/sha256.gobra new file mode 100644 index 0000000..55c8dec --- /dev/null +++ b/ReusableVerificationLibrary/.verification/crypto/sha256/sha256.gobra @@ -0,0 +1,4 @@ +package sha256 + +// empty but necessary since Gobra needs all transitively imported packages +// to perform the global variable analysis diff --git a/ReusableVerificationLibrary/.verification/crypto/x509/x509.gobra b/ReusableVerificationLibrary/.verification/crypto/x509/x509.gobra new file mode 100644 index 0000000..2dceead --- /dev/null +++ b/ReusableVerificationLibrary/.verification/crypto/x509/x509.gobra @@ -0,0 +1,4 @@ +package x509 + +// empty but necessary since Gobra needs all transitively imported packages +// to perform the global variable analysis diff --git a/ReusableVerificationLibrary/.verification/encoding/hex/hex.gobra b/ReusableVerificationLibrary/.verification/encoding/hex/hex.gobra new file mode 100644 index 0000000..be6e6b3 --- /dev/null +++ b/ReusableVerificationLibrary/.verification/encoding/hex/hex.gobra @@ -0,0 +1,4 @@ +package hex + +// empty but necessary since Gobra needs all transitively imported packages +// to perform the global variable analysis diff --git a/ReusableVerificationLibrary/.verification/golang.org/x/crypto/chacha20poly1305/chacha20poly1305.gobra b/ReusableVerificationLibrary/.verification/golang.org/x/crypto/chacha20poly1305/chacha20poly1305.gobra new file mode 100644 index 0000000..ae7e2f1 --- /dev/null +++ b/ReusableVerificationLibrary/.verification/golang.org/x/crypto/chacha20poly1305/chacha20poly1305.gobra @@ -0,0 +1,4 @@ +package chacha20poly1305 + +// empty but necessary since Gobra needs all transitively imported packages +// to perform the global variable analysis diff --git a/ReusableVerificationLibrary/.verification/golang.org/x/crypto/nacl/sign/sign.gobra b/ReusableVerificationLibrary/.verification/golang.org/x/crypto/nacl/sign/sign.gobra new file mode 100644 index 0000000..0d23b6b --- /dev/null +++ b/ReusableVerificationLibrary/.verification/golang.org/x/crypto/nacl/sign/sign.gobra @@ -0,0 +1,4 @@ +package sign + +// empty but necessary since Gobra needs all transitively imported packages +// to perform the global variable analysis diff --git a/ReusableVerificationLibrary/.verification/io/io.gobra b/ReusableVerificationLibrary/.verification/io/io.gobra new file mode 100644 index 0000000..fbd3098 --- /dev/null +++ b/ReusableVerificationLibrary/.verification/io/io.gobra @@ -0,0 +1,4 @@ +package io + +// empty but necessary since Gobra needs all transitively imported packages +// to perform the global variable analysis diff --git a/ReusableVerificationLibrary/.verification/math/big/big.gobra b/ReusableVerificationLibrary/.verification/math/big/big.gobra new file mode 100644 index 0000000..b9ce46d --- /dev/null +++ b/ReusableVerificationLibrary/.verification/math/big/big.gobra @@ -0,0 +1,5 @@ +package big + +type Int struct { + nonEmptyStruct int +} diff --git a/ReusableVerificationLibrary/arbitrary/arbitrary.gobra b/ReusableVerificationLibrary/arbitrary/arbitrary.gobra new file mode 100644 index 0000000..d426b19 --- /dev/null +++ b/ReusableVerificationLibrary/arbitrary/arbitrary.gobra @@ -0,0 +1,48 @@ +package arbitrary + +import ev "github.com/viperproject/ReusableProtocolVerificationLibrary/event" +import "github.com/viperproject/ReusableProtocolVerificationLibrary/label" +import p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" +import tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" +import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" +import u "github.com/viperproject/ReusableProtocolVerificationLibrary/usage" + +// utility package to return an arbitrary value which can then be used for forall introductions. +// these Go functions correspond to Viper methods that do not have any postcondition and thus do not constrain +// their return value. + +decreases +func GetArbTerm() tm.Term + +decreases +func GetArbTraceEntry() tr.TraceEntry + +decreases +func GetArbLabel() label.SecrecyLabel + +decreases +func GetArbPrincipal() p.Principal + +decreases +func GetArbId() p.Id + +decreases +func GetArbEvent() ev.Event + +decreases +func GetArbUsage() u.Usage + +decreases +func GetArbString() string + +decreases +func GetArbUInt32() uint32 + +decreases +func GetArbUInt64() uint64 + +decreases +func GetArbInt() int + +decreases +func GetArbBool() bool diff --git a/ReusableVerificationLibrary/attacker/attacker.gobra b/ReusableVerificationLibrary/attacker/attacker.gobra new file mode 100644 index 0000000..f90e9ee --- /dev/null +++ b/ReusableVerificationLibrary/attacker/attacker.gobra @@ -0,0 +1,33 @@ +package attacker + +import tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" +import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" + +ghost +decreases +pure func attackerKnows(entry tr.TraceEntry, term tm.Term) bool { + return term in attackerKnowledge(entry) +} + +ghost +decreases +requires attackerKnows(t1, term) +requires t1.isSuffix(t2) +ensures attackerKnows(t2, term) +func attackerKnowsMonotonic(t1, t2 tr.TraceEntry, term tm.Term) bool { + t1.getPublicTermsMonotonic(t2) + t1.getMessagePayloadsMonotonic(t2) + t1.getTermsMadePublicMonotonic(t2) +} + +ghost +decreases +pure func isUnknownToAttacker(entry tr.TraceEntry, term tm.Term) bool { + return !attackerKnows(entry, term) +} + +ghost +decreases +pure func attackerKnowledge(entry tr.TraceEntry) set[tm.Term] { + return entry.getPublicTerms() union entry.getMessagePayloads() union entry.getTermsMadePublic() +} diff --git a/ReusableVerificationLibrary/attackercompleteness/attacker-completeness.gobra b/ReusableVerificationLibrary/attackercompleteness/attacker-completeness.gobra new file mode 100644 index 0000000..bc44440 --- /dev/null +++ b/ReusableVerificationLibrary/attackercompleteness/attacker-completeness.gobra @@ -0,0 +1,698 @@ +package attackercompleteness + +import arb "github.com/viperproject/ReusableProtocolVerificationLibrary/arbitrary" +import att "github.com/viperproject/ReusableProtocolVerificationLibrary/attacker" +import ev "github.com/viperproject/ReusableProtocolVerificationLibrary/event" +import "github.com/viperproject/ReusableProtocolVerificationLibrary/label" +import ll "github.com/viperproject/ReusableProtocolVerificationLibrary/labeledlibrary" +import lib "github.com/viperproject/ReusableProtocolVerificationLibrary/labeledlibrary/library" +import "github.com/viperproject/ReusableProtocolVerificationLibrary/labeling" +import p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" +import tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" +import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" +import tri "github.com/viperproject/ReusableProtocolVerificationLibrary/traceinvariant" +import u "github.com/viperproject/ReusableProtocolVerificationLibrary/usage" + + +/** + * This file shows that the attacker can use the same functionality as the + * labeledlibrary, i.e. the attacker can invoke all library functions with + * terms known to him (i.e. public/derivable terms) and will get back terms + * (e.g. the ciphertext in the case of encrypt) that are also derivable. + * This shows that `IsPublishable(t, term)` is a valid invariant for every + * `term` in the the attacker knowledge, i.e. all terms the attacker can + * derive satisfy this invariant. Furthermore, we show record all attacker + * performed operations on the trace and thus prove that the attacker + * maintains the trace invariant. We call all these properties together the + * attacker completeness. + */ + +ghost +requires l.Mem() +ensures l.Mem() +func Attacker(l *ll.LabeledLibrary) { + invariant l.Mem() + for { + // the attacker chooses non-deterministically an operation it wants to perform + nonDeterministicChoice := arb.GetArbInt() + if nonDeterministicChoice == 1 { + CreateNonce(l, arb.GetArbString()) + } else if nonDeterministicChoice == 2 { + // the attacker chooses an arbitrary sender & receiver and takes a message from its knowledge: + arbSender := arb.GetArbPrincipal() + arbReceiver := arb.GetArbPrincipal() + arbMsg := arb.GetArbTerm() + assume att.attackerKnows(l.Snapshot(), arbMsg) + Send(l, arbSender, arbReceiver, arbMsg) + } else if nonDeterministicChoice == 3 { + // there is only a single root entry at the very beginning of the trace so we skip this operation + } else if nonDeterministicChoice == 4 { + // we take 7 arbitrary terms from the attacker knowledge and call `ExtendKnowledge` + // that internally does a non-deterministic choice over all possible terms + t1, t2, t3, t4, t5, t6, t7, t8 := arb.GetArbTerm(), arb.GetArbTerm(), arb.GetArbTerm(), arb.GetArbTerm(), arb.GetArbTerm(), arb.GetArbTerm(), arb.GetArbTerm(), arb.GetArbTerm() + assume att.attackerKnows(l.Snapshot(), t1) && att.attackerKnows(l.Snapshot(), t2) && att.attackerKnows(l.Snapshot(), t3) && att.attackerKnows(l.Snapshot(), t4) && att.attackerKnows(l.Snapshot(), t5) && att.attackerKnows(l.Snapshot(), t6) && att.attackerKnows(l.Snapshot(), t7) && att.attackerKnows(l.Snapshot(), t8) + ExtendKnowledge(l, t1, t2, t3, t4, t5, t6, t7, t8) + } else if nonDeterministicChoice == 5 { + // we take an arbitrary term from the attacker knowledge that we'll deconstruct within + // `ExtendKnowledgeByDeconstruction`: + t := arb.GetArbTerm() + assume att.attackerKnows(l.Snapshot(), t) + ExtendKnowledgeByDeconstruction(l, t) + } else if nonDeterministicChoice == 6 { + arbPrincipal := arb.GetArbPrincipal() + CorruptParticipant(l, arbPrincipal) + } else if nonDeterministicChoice == 7 { + arbSession := arb.GetArbId() + CorruptSession(l, arbSession) + } else if nonDeterministicChoice == 8 { + arbSender := arb.GetArbPrincipal() + arbReceiver := arb.GetArbPrincipal() + arbMsg := arb.GetArbTerm() + // we pick an arbitrary message that has been previously sent: + assume l.Snapshot().messageOccurs(arbSender, arbReceiver, arbMsg) + DropMessage(l, arbSender, arbReceiver, arbMsg) + } else if nonDeterministicChoice == 9 { + arbSender := arb.GetArbPrincipal() + arbReceiver := arb.GetArbPrincipal() + Recv(l, arbSender, arbReceiver) + } + } +} + +// operation 1: create nonce +ghost +requires l.Mem() +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures err == nil ==> att.attackerKnows(l.Snapshot(), nonce) +func CreateNonce(l *ll.LabeledLibrary, usageString string) (nonce tm.Term, err error) { + unfold l.Mem() + var nonceB lib.ByteString + nonceB, err = l.s.CreateNonce(tri.GetLabeling(l.ctx), label.Public(), usageString, set[ev.EventType]{}) + if err == nil { + nonce = tm.random(lib.Abs(nonceB), label.Public(), u.Nonce(usageString)) + l.manager.LogNonce(l.ctx, l.owner, nonce) + fold l.Mem() + l.Publish(nonce) + } else { + fold l.Mem() + } +} + +// operation 2: send message +ghost +requires l.Mem() +requires att.attackerKnows(l.Snapshot(), msg) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures err == nil ==> l.Snapshot().isMessageAt(idSender, idReceiver, msg) +func Send(l *ll.LabeledLibrary, idSender, idReceiver p.Principal, msg tm.Term) (err error) { + msgBytes := lib.NewByteStringWithContent(tm.gamma(msg)) + l.AttackerOnlyKnowsPublishableValues(msg) + unfold l.Mem() + l.manager.LogSend(l.ctx, l.owner, idSender, idReceiver, msg) + snapshot := l.manager.Snapshot(l.ctx, l.owner) + err = l.com.Send(idSender, idReceiver, msgBytes, msg, snapshot) + fold l.Mem() +} + +/** + * operation 3: root entry -- the attacker cannot add this to the trace because there is only + * a unique one at the root of the trace. We demonstrate in the NSL case study as part of + * proving initialization code (i.e. authentic key distribution and creation of the trace by + * appending a root entry) that the root entry satisfies the trace invariant. + */ + +// operation 4: extend attacker knowledge +ghost +requires l.Mem() +requires att.attackerKnows(l.Snapshot(), t1) +requires att.attackerKnows(l.Snapshot(), t2) +requires att.attackerKnows(l.Snapshot(), t3) +requires att.attackerKnows(l.Snapshot(), t4) +requires att.attackerKnows(l.Snapshot(), t5) +requires att.attackerKnows(l.Snapshot(), t6) +requires att.attackerKnows(l.Snapshot(), t7) +requires att.attackerKnows(l.Snapshot(), t8) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures err == nil ==> att.attackerKnows(l.Snapshot(), res) +func ExtendKnowledge(l *ll.LabeledLibrary, t1, t2, t3, t4, t5, t6, t7, t8 tm.Term) (res tm.Term, err error) { + l.AttackerOnlyKnowsPublishableValues(t1) + l.AttackerOnlyKnowsPublishableValues(t2) + l.AttackerOnlyKnowsPublishableValues(t3) + l.AttackerOnlyKnowsPublishableValues(t4) + l.AttackerOnlyKnowsPublishableValues(t5) + l.AttackerOnlyKnowsPublishableValues(t6) + l.AttackerOnlyKnowsPublishableValues(t7) + l.AttackerOnlyKnowsPublishableValues(t8) + + // we do a non-deterministic choice over which operation to apply / term to construct, + // which shows that the attacker can perform all of them while maintaining the trace invariant. + nonDeterministicChoice := arb.GetArbInt() + // we restrict `nonDeterministicChoice` to be within range of a term type such that we + // can exhaustively explore all terms: + { + arbTerm := arb.GetArbTerm() + assume nonDeterministicChoice == tm.getTermType(arbTerm) + assert 0 <= nonDeterministicChoice && nonDeterministicChoice <= 23 + } + + if nonDeterministicChoice == 0 { + // create nonce + res, err = CreateNonce(l, arb.GetArbString()) + } else if nonDeterministicChoice == 1 { + // create tuple2 + l.LabelCtx().tuple2(l.Snapshot(), t1, t2, label.Public()) + res = tm.tuple2(t1, t2) + l.Publish(res) + } else if nonDeterministicChoice == 2 { + // create tuple3 + l.LabelCtx().tuple3(l.Snapshot(), t1, t2, t3, label.Public()) + res = tm.tuple3(t1, t2, t3) + l.Publish(res) + } else if nonDeterministicChoice == 3 { + // create tuple4 + l.LabelCtx().tuple4(l.Snapshot(), t1, t2, t3, t4, label.Public()) + res = tm.tuple4(t1, t2, t3, t4) + l.Publish(res) + } else if nonDeterministicChoice == 4 { + // create tuple5 + l.LabelCtx().tuple5(l.Snapshot(), t1, t2, t3, t4, t5, label.Public()) + res = tm.tuple5(t1, t2, t3, t4, t5) + l.Publish(res) + } else if nonDeterministicChoice == 5 { + // create tuple7 + l.LabelCtx().tuple7(l.Snapshot(), t1, t2, t3, t4, t5, t6, t7, label.Public()) + res = tm.tuple7(t1, t2, t3, t4, t5, t6, t7) + l.Publish(res) + } else if nonDeterministicChoice == 6 { + // create hash + l.LabelCtx().hash(l.Snapshot(), t1, label.Public()) + res = tm.hash(t1) + l.Publish(res) + } else if nonDeterministicChoice == 7 { + // create kdf1 + l.LabelCtx().kdf(l.Snapshot(), t1, label.Public()) + res = tm.kdf1(t1) + l.Publish(res) + } else if nonDeterministicChoice == 8 { + // create kdf2 + l.LabelCtx().kdf(l.Snapshot(), t1, label.Public()) + res = tm.kdf2(t1) + l.Publish(res) + } else if nonDeterministicChoice == 9 { + // create kdf3 + l.LabelCtx().kdf(l.Snapshot(), t1, label.Public()) + res = tm.kdf3(t1) + l.Publish(res) + } else if nonDeterministicChoice == 10 { + nonDeterministicPkChoice := arb.GetArbInt() + assume 0 <= nonDeterministicPkChoice && nonDeterministicPkChoice < 3 + + if nonDeterministicPkChoice == 0 { + // create pk + l.LabelCtx().createPk(l.Snapshot(), t1, label.Public()) + res = tm.createPk(t1) + l.Publish(res) + } else if nonDeterministicPkChoice == 1 { + arbUsageString := arb.GetArbString() + res, err = GeneratePkeKey(l, arbUsageString) + } else { + arbUsageString := arb.GetArbString() + res, err = GenerateDHKey(l, arbUsageString) + } + } else if nonDeterministicChoice == 11 { + shouldEncrypt := arb.GetArbBool() + if shouldEncrypt { + // create encrypt + res, err = Enc(l, t1, t2) + } else { + // we also show that decryption is possible: + res, err = Dec(l, t3, t4) + } + } else if nonDeterministicChoice == 12 { + shouldEncrypt := arb.GetArbBool() + if shouldEncrypt { + // create aead + res, err = AeadEnc(l, t1, t2, t3, t4) + } else { + // we also show that decryption is possible: + res, err = AeadDec(l, t5, t6, t7, t8) + } + } else if nonDeterministicChoice == 13 { + // create const1 + l.LabelCtx().const1(l.Snapshot(), label.Public()) + res = tm.const1() + l.Publish(res) + } else if nonDeterministicChoice == 14 { + // create exp + l.LabelCtx().exp(l.Snapshot(), t1, t2, label.Public()) + res = tm.exp(t1, t2) + l.Publish(res) + } else if nonDeterministicChoice == 15 { + // create mult + l.LabelCtx().mult(l.Snapshot(), t1, t2, label.Public()) + res = tm.mult(t1, t2) + l.Publish(res) + } else if nonDeterministicChoice == 16 { + // create string constant + arbString := arb.GetArbString() + l.LabelCtx().stringTerm(l.Snapshot(), arbString, label.Public()) + res = tm.stringTerm(arbString) + l.Publish(res) + } else if nonDeterministicChoice == 17 { + // create zero string + arbLength := arb.GetArbInt() + l.LabelCtx().zeroString(l.Snapshot(), arbLength, label.Public()) + res = tm.zeroString(arbLength) + l.Publish(res) + } else if nonDeterministicChoice == 18 { + // create integer64 + arbValue := arb.GetArbUInt64() + l.LabelCtx().integer64(l.Snapshot(), arbValue, label.Public()) + res = tm.integer64(arbValue) + l.Publish(res) + } else if nonDeterministicChoice == 19 { + // create integer32 + arbValue := arb.GetArbUInt32() + l.LabelCtx().integer32(l.Snapshot(), arbValue, label.Public()) + res = tm.integer32(arbValue) + l.Publish(res) + } else if nonDeterministicChoice == 20 { + // create info term + l.LabelCtx().infoTerm(l.Snapshot(), label.Public()) + res = tm.infoTerm() + l.Publish(res) + } else if nonDeterministicChoice == 21 { + // create prologue term + l.LabelCtx().prologueTerm(l.Snapshot(), label.Public()) + res = tm.prologueTerm() + l.Publish(res) + } else if nonDeterministicChoice == 22 { + // create DH generator + l.LabelCtx().generator(l.Snapshot(), label.Public()) + res = tm.generator() + l.Publish(res) + } else if nonDeterministicChoice == 23 { + shouldSign := arb.GetArbBool() + if shouldSign { + // create sign + res, err = Sign(l, t1, t2) + } else { + // we also show that open is possible: + res, err = Open(l, t3, t4) + } + } else { + // sanity check that we have not missed any term by checking that + // we have covered all term types + assert nonDeterministicChoice < 0 || 23 < nonDeterministicChoice + assert false + } + return +} + +/** + * with `GeneratePkeKey`, `GenerateDHKey`, `Enc`, `Dec`, `AeadEnc`, and `AeadDec` + * we show that the attacker can invoke the corresponding cryptographic operations, + * i.e. satisfy the corresponding preconditions and publish the output's term + * representation + */ +ghost +requires l.Mem() +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures err == nil ==> att.attackerKnows(l.Snapshot(), sk) +func GeneratePkeKey(l *ll.LabeledLibrary, usageString string) (sk tm.Term, err error) { + unfold l.Mem() + var skB lib.ByteString + _, skB, err = l.s.GeneratePkeKey(tri.GetLabeling(l.ctx), label.Public(), usageString, set[ev.EventType]{}) + if err == nil { + sk = tm.random(lib.Abs(skB), label.Public(), u.PkeKey(usageString)) + l.manager.LogNonce(l.ctx, l.owner, sk) + fold l.Mem() + l.Publish(sk) + } else { + fold l.Mem() + } +} + +ghost +requires l.Mem() +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures err == nil ==> att.attackerKnows(l.Snapshot(), sk) +func GenerateDHKey(l *ll.LabeledLibrary, usageString string) (sk tm.Term, err error) { + unfold l.Mem() + var skB lib.ByteString + skB, err = l.s.GenerateDHKey(tri.GetLabeling(l.ctx), label.Public(), usageString, set[ev.EventType]{}) + if err == nil { + sk = tm.random(lib.Abs(skB), label.Public(), u.DhKey(usageString)) + l.manager.LogNonce(l.ctx, l.owner, sk) + fold l.Mem() + l.Publish(sk) + } else { + fold l.Mem() + } +} + +ghost +requires l.Mem() +requires att.attackerKnows(l.Snapshot(), pk) +requires att.attackerKnows(l.Snapshot(), plaintext) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures err == nil ==> att.attackerKnows(l.Snapshot(), ciphertext) +func Enc(l *ll.LabeledLibrary, pk, plaintext tm.Term) (ciphertext tm.Term, err error) { + pkBytes := lib.NewByteStringWithContent(tm.gamma(pk)) + plaintextBytes := lib.NewByteStringWithContent(tm.gamma(plaintext)) + l.AttackerOnlyKnowsPublishableValues(pk) + l.AttackerOnlyKnowsPublishableValues(plaintext) + _, err = l.Enc(plaintextBytes, pkBytes, plaintext, pk) + if err == nil { + ciphertext = tm.encrypt(plaintext, pk) + l.Publish(ciphertext) + } +} + +ghost +requires l.Mem() +requires att.attackerKnows(l.Snapshot(), ciphertext) +requires att.attackerKnows(l.Snapshot(), sk) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures err == nil ==> att.attackerKnows(l.Snapshot(), plaintext) +func Dec(l *ll.LabeledLibrary, ciphertext, sk tm.Term) (plaintext tm.Term, err error) { + skBytes := lib.NewByteStringWithContent(tm.gamma(sk)) + ciphertextBytes := lib.NewByteStringWithContent(tm.gamma(ciphertext)) + l.AttackerOnlyKnowsPublishableValues(ciphertext) + l.AttackerOnlyKnowsPublishableValues(sk) + skOwner := arb.GetArbId() + l.LabelCtx().CanDecryptWithPublicSk(l.Snapshot(), ciphertext, sk, skOwner) + _, err = l.Dec(ciphertextBytes, skBytes, ciphertext, sk, skOwner) + if err != nil { + return + } + optPlaintext := tm.decrypt(ciphertext, sk) + if optPlaintext == none[tm.Term] { + // the attacker can use the labeled decryption function + // however, the attacker will only obtain guarantees on the byte-level. + // we model the attacker to operate on symbolic terms. + // thus, we additionally call decrypt on the term-level and abort + // if decryption fails + err = lib.NewError("decryption failed") + return + } + plaintext = get(optPlaintext) + l.LabelCtx().PlaintextIsPublishableForPublicSk(l.Snapshot(), plaintext, sk, skOwner) + l.Publish(plaintext) +} + +ghost +requires l.Mem() +requires att.attackerKnows(l.Snapshot(), key) +requires att.attackerKnows(l.Snapshot(), nonce) +requires att.attackerKnows(l.Snapshot(), plaintext) +requires att.attackerKnows(l.Snapshot(), additionalData) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures err == nil ==> att.attackerKnows(l.Snapshot(), ciphertext) +func AeadEnc(l *ll.LabeledLibrary, key, nonce, plaintext, additionalData tm.Term) (ciphertext tm.Term, err error) { + keyBytes := lib.NewByteStringWithContent(tm.gamma(key)) + nonceBytes := lib.NewByteStringWithContent(tm.gamma(nonce)) + plaintextBytes := lib.NewByteStringWithContent(tm.gamma(plaintext)) + additionalDataBytes := lib.NewByteStringWithContent(tm.gamma(additionalData)) + l.AttackerOnlyKnowsPublishableValues(key) + l.AttackerOnlyKnowsPublishableValues(nonce) + l.AttackerOnlyKnowsPublishableValues(plaintext) + l.AttackerOnlyKnowsPublishableValues(additionalData) + if lib.Size(keyBytes) != 32 || lib.Size(nonceBytes) != 12 { + err = lib.NewError("key or nonce have invalid length") + return + } + _, err = l.AeadEnc(keyBytes, nonceBytes, plaintextBytes, additionalDataBytes, key, nonce, plaintext, additionalData, label.Public()) + if err == nil { + ciphertext = tm.aead(key, nonce, plaintext, additionalData) + l.Publish(ciphertext) + } +} + +ghost +requires l.Mem() +requires att.attackerKnows(l.Snapshot(), key) +requires att.attackerKnows(l.Snapshot(), nonce) +requires att.attackerKnows(l.Snapshot(), ciphertext) +requires att.attackerKnows(l.Snapshot(), additionalData) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures err == nil ==> att.attackerKnows(l.Snapshot(), plaintext) +func AeadDec(l *ll.LabeledLibrary, key, nonce, ciphertext, additionalData tm.Term) (plaintext tm.Term, err error) { + keyBytes := lib.NewByteStringWithContent(tm.gamma(key)) + nonceBytes := lib.NewByteStringWithContent(tm.gamma(nonce)) + ciphertextBytes := lib.NewByteStringWithContent(tm.gamma(ciphertext)) + additionalDataBytes := lib.NewByteStringWithContent(tm.gamma(additionalData)) + l.AttackerOnlyKnowsPublishableValues(key) + l.AttackerOnlyKnowsPublishableValues(nonce) + l.AttackerOnlyKnowsPublishableValues(ciphertext) + l.AttackerOnlyKnowsPublishableValues(additionalData) + if lib.Size(keyBytes) != 32 || lib.Size(nonceBytes) != 12 { + err = lib.NewError("key or nonce have invalid length") + return + } + l.LabelCtx().CanAeadDecryptWithPublishableKey(l.Snapshot(), key, nonce, ciphertext, additionalData, label.Public()) + _, err = l.AeadDec(keyBytes, nonceBytes, ciphertextBytes, additionalDataBytes, key, nonce, ciphertext, additionalData, label.Public()) + if err != nil { + return + } + optPlaintext := tm.AeadDecrypt(ciphertext, key, nonce, additionalData) + if optPlaintext == none[tm.Term] { + // the attacker can use the labeled decryption function + // however, the attacker will only obtain guarantees on the byte-level. + // we model the attacker to operate on symbolic terms. + // thus, we additionally call decrypt on the term-level and abort + // if decryption fails + err = lib.NewError("decryption failed") + return + } + plaintext = get(optPlaintext) + l.LabelCtx().PlaintextIsPublishableForPublishableKey(l.Snapshot(), key, nonce, plaintext, additionalData, label.Public()) + l.Publish(plaintext) +} + +ghost +requires l.Mem() +requires att.attackerKnows(l.Snapshot(), msg) +requires att.attackerKnows(l.Snapshot(), sk) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures err == nil ==> att.attackerKnows(l.Snapshot(), signedMsg) +func Sign(l *ll.LabeledLibrary, msg, sk tm.Term) (signedMsg tm.Term, err error) { + msgBytes := lib.NewByteStringWithContent(tm.gamma(msg)) + skBytes := lib.NewByteStringWithContent(tm.gamma(sk)) + l.AttackerOnlyKnowsPublishableValues(msg) + l.AttackerOnlyKnowsPublishableValues(sk) + skOwner := arb.GetArbId() + _, err = l.Sign(msgBytes, skBytes, msg, sk, skOwner) + if err == nil { + signedMsg = tm.sign(msg, sk) + l.Publish(signedMsg) + } +} + +ghost +requires l.Mem() +requires att.attackerKnows(l.Snapshot(), signedMsg) +requires att.attackerKnows(l.Snapshot(), pk) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures err == nil ==> att.attackerKnows(l.Snapshot(), msg) +func Open(l *ll.LabeledLibrary, signedMsg, pk tm.Term) (msg tm.Term, err error) { + signedMsgBytes := lib.NewByteStringWithContent(tm.gamma(signedMsg)) + pkBytes := lib.NewByteStringWithContent(tm.gamma(pk)) + l.AttackerOnlyKnowsPublishableValues(signedMsg) + l.AttackerOnlyKnowsPublishableValues(pk) + // the following call expects the secret key term corresponding to the provided + // public key as a ghost argument. + // we simply pick some term such that this term's corresponding public key has + // the same byte-level representation as `pk`: + sk := arb.GetArbTerm() + assume lib.Abs(pkBytes) == tm.gamma(tm.createPk(sk)) + skOwner := arb.GetArbId() + _, err = l.Open(signedMsgBytes, pkBytes, signedMsg, sk, skOwner) + if err != nil { + return + } + optMsg := tm.Open(signedMsg, pk) + if optMsg == none[tm.Term] { + // the attacker can use the labeled open function + // however, the attacker will only obtain guarantees on the byte-level. + // we model the attacker to operate on symbolic terms. + // thus, we additionally call open on the term-level and abort + // if signature verification fails + err = lib.NewError("signature verification failed") + return + } + msg = get(optMsg) + l.LabelCtx().UnsignedDataIsPublishableIfSignatureIsPublishable(l.Snapshot(), msg, tm.getSk(pk)) + l.Publish(msg) +} + +// operation 5: deconstruct term in the attacker knowledge +ghost +requires l.Mem() +requires att.attackerKnows(l.Snapshot(), t) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +func ExtendKnowledgeByDeconstruction(l *ll.LabeledLibrary, t tm.Term) { + l.AttackerOnlyKnowsPublishableValues(t) + + // we get a non-deterministic term `t` as input. + // we case split over all terms that can be deconstructed according to our + // perfect cryptography assumptions and show that all resulting terms can be + // added to the attacker knowledge while maintaining the trace invariant. + + t1, t2, t3, t4, t5, t6, t7 := arb.GetArbTerm(), arb.GetArbTerm(), arb.GetArbTerm(), arb.GetArbTerm(), arb.GetArbTerm(), arb.GetArbTerm(), arb.GetArbTerm() + snap := l.Snapshot() + + // random cannot be deconstructed further + if t == tm.tuple2(t1, t2) { + l.LabelCtx().untuple2(snap, t1, t2, label.Public()) + MultiPublish(l, seq[tm.Term]{ t1, t2 }) + } else if t == tm.tuple3(t1, t2, t3) { + l.LabelCtx().untuple3(snap, t1, t2, t3, label.Public()) + MultiPublish(l, seq[tm.Term]{ t1, t2, t3 }) + } else if t == tm.tuple4(t1, t2, t3, t4) { + l.LabelCtx().untuple4(snap, t1, t2, t3, t4, label.Public()) + MultiPublish(l, seq[tm.Term]{ t1, t2, t3, t4 }) + } else if t == tm.tuple5(t1, t2, t3, t4, t5) { + l.LabelCtx().untuple5(snap, t1, t2, t3, t4, t5, label.Public()) + MultiPublish(l, seq[tm.Term]{ t1, t2, t3, t4, t5 }) + } else if t == tm.tuple7(t1, t2, t3, t4, t5, t6, t7) { + l.LabelCtx().untuple7(snap, t1, t2, t3, t4, t5, t6, t7, label.Public()) + MultiPublish(l, seq[tm.Term]{ t1, t2, t3, t4, t5, t6, t7 }) + } + + // we assume hash, kdf1, kdf2, kdf3, and createPk cannot be deconstructed + // due to our perfect cryptography assumption. + + // we already check the deconstruction of encrypt and aead (if decryption succeeds) + // as part of `ExtendKnowledge` above + + // const1, stringTerm, zeroString, integer64, integer32, infoTerm, prologueTerm, and generator cannot be further deconstructed to other terms. + + // we assume for mult and exp (which are used for Diffie-Hellman exponentiation) that these cannot be deconstructed either. +} + +ghost +requires l.Mem() +requires forall i uint :: { terms[i] } 0 <= i && i < len(terms) ==> l.LabelCtx().IsPublishable(l.Snapshot(), terms[i]) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +func MultiPublish(l *ll.LabeledLibrary, terms seq[tm.Term]) { + snap := l.Snapshot() + + invariant 0 <= i && i <= len(terms) + invariant l.Mem() + invariant forall j uint :: { terms[j] } i <= j && j < len(terms) ==> l.LabelCtx().IsPublishable(snap, terms[j]) + invariant snap.isSuffix(l.Snapshot()) + invariant l.ImmutableState() == old(l.ImmutableState()) + for i := 0; i < len(terms); i++ { + snap1 := l.Snapshot() + l.LabelCtx().IsPublishableMonotonic(snap, snap1, terms[i]) + l.Publish(terms[i]) + snap.isSuffixTransitive(snap1, l.Snapshot()) + } +} + +// operation 6: participant corruption +ghost +requires l.Mem() +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures p.principalId(principal) in l.Snapshot().getCorruptIds() +ensures tr.containsCorruptId(l.Snapshot().getCorruptIds(), set[p.Id]{ p.principalId(principal) }) +ensures att.attackerKnows(l.Snapshot(), arbTerm) +func CorruptParticipant(l *ll.LabeledLibrary, principal p.Principal) (arbTerm tm.Term) { + principalId := p.principalId(principal) + labelCtx := l.LabelCtx() + + // we show now that we can add an arbitrary term that `principal` can read (i.e. is possibly in its long-term state) + // to the attacker knowledge after recording the corruption on the trace: + arbTerm = arb.GetArbTerm() + assume labelCtx.IsMsg(l.Snapshot(), arbTerm, label.Readers(set[p.Id]{ principalId })) + + unfold l.Mem() + l.manager.LogCorruption(l.ctx, l.owner, principalId) + fold l.Mem() + + labelCtx.IsMsgMonotonic(old(l.Snapshot()), l.Snapshot(), arbTerm, label.Readers(set[p.Id]{ principalId })) + labelCtx.CanFlowTransitive(l.Snapshot(), labelCtx.GetLabel(arbTerm), label.Readers(set[p.Id]{ principalId }), label.Public()) + s1 := l.Snapshot() + l.Publish(arbTerm) + s1.getCorruptIdsMonotonic(l.Snapshot()) +} + +// operation 7: session corruption +ghost +requires l.Mem() +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures sessionId in l.Snapshot().getCorruptIds() +ensures tr.containsCorruptId(l.Snapshot().getCorruptIds(), set[p.Id]{ sessionId }) +ensures sessionId in l.Snapshot().getCorruptIds() +func CorruptSession(l *ll.LabeledLibrary, sessionId p.Id) (arbTerm tm.Term) { + labelCtx := l.LabelCtx() + + // we show now that we can add an arbitrary term that `sessionId` can read (i.e. is possibly in its long-term or ephemeral/session state) + // to the attacker knowledge after recording the corruption on the trace: + arbTerm = arb.GetArbTerm() + assume labelCtx.IsMsg(l.Snapshot(), arbTerm, label.Readers(set[p.Id]{ sessionId })) + + unfold l.Mem() + l.manager.LogCorruption(l.ctx, l.owner, sessionId) + fold l.Mem() + + labelCtx.IsMsgMonotonic(old(l.Snapshot()), l.Snapshot(), arbTerm, label.Readers(set[p.Id]{ sessionId })) + labelCtx.CanFlowTransitive(l.Snapshot(), labelCtx.GetLabel(arbTerm), label.Readers(set[p.Id]{ sessionId }), label.Public()) + s1 := l.Snapshot() + l.Publish(arbTerm) + s1.getCorruptIdsMonotonic(l.Snapshot()) +} + +// operation 8: drop message +ghost +requires l.Mem() +requires l.Snapshot().messageOccurs(idSender, idReceiver, msg) // only previously sent message can be dropped +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot().isMessageDroppedAt(idSender, idReceiver, msg) +func DropMessage(l *ll.LabeledLibrary, idSender, idReceiver p.Principal, msg tm.Term) { + msgBytes := lib.NewByteStringWithContent(tm.gamma(msg)) + l.MessageOccursImpliesMessageInv(idSender, idReceiver, msg) + unfold l.Mem() + l.manager.LogMsgDrop(l.ctx, l.owner, idSender, idReceiver, msg) + // the message drop has been recorded on the trace. A real attacker would now issue some command to the + // network to perform the actual I/O operation for dropping the message. + fold l.Mem() +} + +// `Recv` shows that the attacker can read messages from the network and also add them +// to its attacker knowledge (similarly to how the attacker adds a term derived by +// function application to its knowledge) +ghost +requires l.Mem() +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures err == nil ==> att.attackerKnows(l.Snapshot(), msg) +func Recv(l *ll.LabeledLibrary, idSender, idReceiver p.Principal) (err error, msg tm.Term) { + snapshot := l.Snapshot() + unfold l.Mem() + _, err, msg = l.com.Receive(idSender, idReceiver, snapshot) + fold l.Mem() + ghost if err == nil { + prev := l.MessageOccursImpliesMessageInv(idSender, idReceiver, msg) + (tr.getPrev(prev)).isSuffixTransitive(prev, l.Snapshot()) + tri.messageInvTransitive(l.Ctx(), idSender, idReceiver, msg, tr.getPrev(prev), l.Snapshot()) + l.Publish(msg) + } +} diff --git a/ReusableVerificationLibrary/concurrentdatastructure/concurrent-datastructure.gobra b/ReusableVerificationLibrary/concurrentdatastructure/concurrent-datastructure.gobra new file mode 100644 index 0000000..2608d2d --- /dev/null +++ b/ReusableVerificationLibrary/concurrentdatastructure/concurrent-datastructure.gobra @@ -0,0 +1,336 @@ +package concurrentdatastructure + +import "sync" +import arb "github.com/viperproject/ReusableProtocolVerificationLibrary/arbitrary" +import p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" +import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" + +type ClientId = p.Id +type ValueType = tr.TraceEntry + +type ClientHistoryMutex struct { + mutex *sync.Mutex + /** each client has a last seen value */ + snapshots map[ClientId]*ValueType + currentValue *ValueType +} + +pred (m *ClientHistoryMutex) ClientHistoryMutexStateInit() { + acc(m) && + acc(m.mutex) && *m.mutex == sync.Mutex{} && + acc(m.snapshots) && len(m.snapshots) == 0 && + acc(m.currentValue) +} + +pred (m *ClientHistoryMutex) ClientHistoryMutexStateInitWithInv(inv ClientHistoryInv, ghost clients set[ClientId]) { + acc(m) && acc(m.snapshots) && + acc(m.mutex.LockP(), _) && m.mutex.LockInv() == ClientHistoryMutexInv! && + clients == domain(m.snapshots) && + (forall client1, client2 ClientId :: client1 in domain(m.snapshots) && client2 in domain(m.snapshots) && client1 != client2 ==> (m.snapshots)[client1] != (m.snapshots)[client2]) && + (forall snapshot *ValueType :: snapshot in range(m.snapshots) ==> acc(snapshot, 1/2)) +} + +/** represents the state in the mutex's unlocked state */ +pred (m *ClientHistoryMutex) ClientHistoryMutexState(inv ClientHistoryInv, client ClientId) { + // the idea is that each client get 1/len(clients) many permissions + acc(m, _) && acc(m.snapshots, _) && inv != nil && + acc(m.mutex.LockP(), _) && m.mutex.LockInv() == ClientHistoryMutexInv! && + client in domain(m.snapshots) && + // remaining 1/2 permission to value in snapshot belonging to current client: + acc((m.snapshots)[client], 1/2) +} + +/** represents the state in the mutex's locked state */ +pred (m *ClientHistoryMutex) ClientHistoryMutexStateLocked(inv ClientHistoryInv, client ClientId, currentValue ValueType) { + // beginning copy of clientHistoryMutexState + acc(m, _) && acc(m.snapshots, _) && inv != nil && + acc(m.mutex.LockP(), _) && m.mutex.LockInv() == ClientHistoryMutexInv! && + client in domain(m.snapshots) && + // remaining 1/2 permission to value in snapshot belonging to current client: + acc((m.snapshots)[client], 1/2) && + // end of copy of clientHistoryMutexState + m.mutex.UnlockP() && + acc(m.currentValue) && + *m.currentValue == currentValue && + (forall snapshot *ValueType :: { inv.TwoStepValueInv(*snapshot, *m.currentValue) } snapshot in range(m.snapshots) ==> acc(snapshot, 1/2) && inv.TwoStepValueInv(*snapshot, *m.currentValue)) +} + +/** represents the invariant that is associated with the mutex */ +pred ClientHistoryMutexInv(inv ClientHistoryInv, currentValue *ValueType, ghost snapshots set[*ValueType]) { + inv != nil && acc(currentValue) && + inv.CurrentValueInv(*currentValue) && + (forall snapshot *ValueType :: { inv.TwoStepValueInv(*snapshot, *currentValue) } snapshot in snapshots ==> + acc(snapshot, 1/2) && inv.TwoStepValueInv(*snapshot, *currentValue)) +} + +type ClientHistoryInv interface { + /** invariant to specify properties that hold for the current value */ + pred CurrentValueInv(value ValueType) + + /** two step invariant */ + ghost + decreases + pure TwoStepValueInv(oldValue ValueType, currentValue ValueType) bool + + /** proof obligation to show that the two step invariant is reflexive */ + ghost + decreases + ensures TwoStepValueInv(value, value) + TwoStepValueInvReflexive(value ValueType) + + /** proof obligation to show that the two step invariant is transitive */ + ghost + decreases + requires TwoStepValueInv(val1, val2) && TwoStepValueInv(val2, val3) + ensures TwoStepValueInv(val1, val3) + TwoStepValueInvTransitive(val1, val2, val3 ValueType) +} + +// indirection for ClientHistoryInv that gives us the property that +// `inv` is non-nil +decreases +requires acc(m.ClientHistoryMutexStateLocked(inv, client, currentValue), _) +ensures inv != nil +pure func (m *ClientHistoryMutex) GetInvLocked(inv ClientHistoryInv, client ClientId, currentValue ValueType) ClientHistoryInv { + return unfolding acc(m.ClientHistoryMutexStateLocked(inv, client, currentValue), _) in inv +} + +decreases +requires acc(m.ClientHistoryMutexState(inv, client), _) +pure func (m *ClientHistoryMutex) LastSeenValue(inv ClientHistoryInv, client ClientId) ValueType { + return unfolding acc(m.ClientHistoryMutexState(inv, client), _) in *(m.snapshots)[client] +} + +decreases +requires acc(m.ClientHistoryMutexStateLocked(inv, client, currentValue), _) +pure func (m *ClientHistoryMutex) LastSeenValueLocked(inv ClientHistoryInv, client ClientId, currentValue ValueType) ValueType { + return unfolding acc(m.ClientHistoryMutexStateLocked(inv, client, currentValue), _) in *(m.snapshots)[client] +} + +ghost +decreases +requires acc(m.ClientHistoryMutexState(inv, client), _) +pure func (m *ClientHistoryMutex) Clients(inv ClientHistoryInv, client ClientId) set[ClientId] { + return unfolding acc(m.ClientHistoryMutexState(inv, client), _) in domain(m.snapshots) +} + +ghost +decreases +requires acc(m.ClientHistoryMutexStateLocked(inv, client, currentValue), _) +pure func (m *ClientHistoryMutex) ClientsLocked(inv ClientHistoryInv, client ClientId, currentValue ValueType) set[ClientId] { + return unfolding acc(m.ClientHistoryMutexStateLocked(inv, client, currentValue), _) in domain(m.snapshots) +} + +ghost +decreases +requires acc(m.ClientHistoryMutexState(inv, client1), 1/16) +requires acc(m.ClientHistoryMutexState(inv, client2), 1/16) +ensures acc(m.ClientHistoryMutexState(inv, client1), 1/16) +ensures acc(m.ClientHistoryMutexState(inv, client2), 1/16) +ensures m.Clients(inv, client1) == m.Clients(inv, client2) +func (m *ClientHistoryMutex) ClientsAreIdentical(inv ClientHistoryInv, client1, client2 ClientId) { + assert unfolding acc(m.ClientHistoryMutexState(inv, client1), 1/16) in unfolding acc(m.ClientHistoryMutexState(inv, client2), 1/16) in true +} + +ghost +decreases +requires acc(m.ClientHistoryMutexStateLocked(inv, client1, currentValue1), 1/16) +requires acc(m.ClientHistoryMutexState(inv, client2), 1/16) +ensures acc(m.ClientHistoryMutexStateLocked(inv, client1, currentValue1), 1/16) +ensures acc(m.ClientHistoryMutexState(inv, client2), 1/16) +ensures m.ClientsLocked(inv, client1, currentValue1) == m.Clients(inv, client2) +func (m *ClientHistoryMutex) ClientsAreIdenticalLocked1(inv ClientHistoryInv, client1, client2 ClientId, currentValue1 ValueType) { + assert unfolding acc(m.ClientHistoryMutexStateLocked(inv, client1, currentValue1), 1/16) in unfolding acc(m.ClientHistoryMutexState(inv, client2), 1/16) in true +} + +decreases +ensures m.ClientHistoryMutexStateInit() +func NewClientHistoryMutex() (m *ClientHistoryMutex) { + m = &ClientHistoryMutex{} + m.mutex = &sync.Mutex{} + var value@ ValueType + m.currentValue = &value + m.snapshots = make(map[ClientId]*ValueType) + fold m.ClientHistoryMutexStateInit() +} + +decreases +requires m.ClientHistoryMutexStateInit() +requires inv != nil && isComparable(inv) +requires noPerm < p && p <= writePerm +requires 0 < len(clients) +requires forall j int :: { clients[j] } 0 <= j && j < len(clients) ==> acc(&clients[j], p) +requires forall j, k int :: { clients[j], clients[k] } 0 <= j && j < k && k < len(clients) ==> clients[j] != clients[k] +requires inv.CurrentValueInv(initVal) +ensures forall j int :: { clients[j] } 0 <= j && j < len(clients) ==> acc(&clients[j], p) +ensures forall j, k int :: { clients[j], clients[k] } 0 <= j && j < k && k < len(clients) ==> clients[j] != clients[k] +ensures forall j int :: { clients[j] } 0 <= j && j < len(clients) ==> m.ClientHistoryMutexState(inv, clients[j]) +ensures forall j, k int :: { clients[j], clients[k] } 0 <= j && j < len(clients) && 0 <= k && k < len(clients) ==> clients[k] in m.Clients(inv, clients[j]) +func (m *ClientHistoryMutex) SetInv(inv ClientHistoryInv, clients []ClientId, initVal ValueType, ghost p perm) { + unfold m.ClientHistoryMutexStateInit() + + /** sequence representation of clients */ + clientSeq := seq[ClientId]{} + + invariant 0 <= i && i <= len(clients) + // we have permissions to m, its fields, and the clients (which are pairwise disjoint): + invariant acc(m, 1/2) && acc(m.snapshots) + invariant forall j int :: { clients[j] } 0 <= j && j < len(clients) ==> acc(&clients[j], p) + invariant forall j, k int :: { clients[j], clients[k] } 0 <= j && j < k && k < len(clients) ==> clients[j] != clients[k] + // clientSeq becomes more and more an abstraction of `clients`: + invariant len(clientSeq) == i + invariant forall j int :: { clientSeq[j] } 0 <= j && j < i ==> clients[j] == clientSeq[j] + invariant domain(m.snapshots) == set(clientSeq) + // each client has its own snapshot pointer and each snapshot satisfies the invariant: + invariant forall snapshot *ValueType :: { inv.TwoStepValueInv(*snapshot, initVal) } snapshot in range(m.snapshots) ==> acc(snapshot) && inv.TwoStepValueInv(*snapshot, initVal) + invariant forall client1, client2 ClientId :: { (m.snapshots)[client1], (m.snapshots)[client2] } client1 in domain(m.snapshots) && client2 in domain(m.snapshots) && client1 != client2 ==> (m.snapshots)[client1] != (m.snapshots)[client2] + decreases len(clients) - i + for i := 0; i < len(clients); i++ { + client := clients[i] + clientSeq = clientSeq ++ seq[ClientId]{ client } + clientValue@ := initVal + length := len(m.snapshots) + assert !(client in domain(m.snapshots)) + (m.snapshots)[client] = &clientValue + assert len(m.snapshots) == length + 1 + inv.TwoStepValueInvReflexive(initVal) + } + + *m.currentValue = initVal + fold ClientHistoryMutexInv!() + m.mutex.SetInv(ClientHistoryMutexInv!) + + fold m.ClientHistoryMutexStateInitWithInv(inv, set(clientSeq)) + m.split(inv, clientSeq) + // the following assert stmt is necessary: + assert forall j int :: { clients[j] } 0 <= j && j < len(clients) ==> clients[j] == clientSeq[j] +} + +ghost +decreases +requires inv != nil && 0 < len(clients) +requires forall j, k int :: { clients[j], clients[k] } 0 <= j && j < k && k < len(clients) ==> clients[j] != clients[k] +requires m.ClientHistoryMutexStateInitWithInv(inv, set(clients)) +ensures forall j int :: { clients[j] } 0 <= j && j < len(clients) ==> m.ClientHistoryMutexState(inv, clients[j]) +ensures forall j int :: { clients[j] } 0 <= j && j < len(clients) ==> set(clients) subset m.Clients(inv, clients[j]) +func (m *ClientHistoryMutex) split(inv ClientHistoryInv, ghost clients seq[ClientId]) { + unfold m.ClientHistoryMutexStateInitWithInv(inv, set(clients)) + + // some abbreviations for the following loop: + currentValue := m.currentValue + mutex := m.mutex + + invariant 0 <= i && i <= len(clients) + invariant forall j, k int :: { clients[j], clients[k] } 0 <= j && j < k && k < len(clients) ==> clients[j] != clients[k] + invariant acc(m, _) + invariant m.currentValue == currentValue && m.mutex == mutex + invariant acc(m.snapshots, _) + invariant acc(mutex.LockP(), _) && mutex.LockInv() == ClientHistoryMutexInv!; + invariant forall j int :: { clients[j] } 0 <= j && j < len(clients) ==> clients[j] in domain(m.snapshots) + invariant forall client1, client2 ClientId :: { (m.snapshots)[client1], (m.snapshots)[client2] } client1 in domain(m.snapshots) && client2 in domain(m.snapshots) && client1 != client2 ==> (m.snapshots)[client1] != (m.snapshots)[client2] + invariant forall j int :: { clients[j] } i <= j && j < len(clients) ==> acc((m.snapshots)[clients[j]], 1/2) + // in each iteration an additional predicate instance is constructed: + invariant forall j int :: { clients[j] } 0 <= j && j < i ==> m.ClientHistoryMutexState(inv, clients[j]) + invariant forall j int :: { clients[j] } 0 <= j && j < i ==> set(clients) subset m.Clients(inv, clients[j]) + decreases len(clients) - i + for i := 0; i < len(clients); i++ { + client := clients[i] + fold m.ClientHistoryMutexState(inv, client) + } +} + +// TODO prove termination. This is currently not possible because we cannot show +// that `Lock` eventually succeeds. +decreases _ +requires m.ClientHistoryMutexState(inv, client) +ensures m.ClientHistoryMutexStateLocked(inv, client, currentValue) && inv != nil +ensures inv.TwoStepValueInv(m.LastSeenValueLocked(inv, client, currentValue), currentValue) +ensures inv.CurrentValueInv(currentValue) +// LastSeenValue remains unchanged: +ensures m.LastSeenValueLocked(inv, client, currentValue) == old(m.LastSeenValue(inv, client)) +ensures m.ClientsLocked(inv, client, currentValue) == old(m.Clients(inv, client)) +func (m *ClientHistoryMutex) Lock(inv ClientHistoryInv, client ClientId) (currentValue ValueType) { + unfold m.ClientHistoryMutexState(inv, client) + m.mutex.Lock() + unfold ClientHistoryMutexInv!() + currentValue = *m.currentValue + fold m.ClientHistoryMutexStateLocked(inv, client, currentValue) +} + +ghost +decreases +requires m.ClientHistoryMutexStateLocked(inv, client, currentValue) +requires m.GetInvLocked(inv, client, currentValue).TwoStepValueInv(m.LastSeenValueLocked(inv, client, currentValue), currentValue) +requires inv.TwoStepValueInv(currentValue, newValue) +// the following precondition enforces that the global trace can only be extended by up to a single trace entry: +requires newValue == currentValue || tr.getPrev(newValue) == currentValue +requires inv.CurrentValueInv(newValue) +ensures m.ClientHistoryMutexState(inv, client) +ensures unfolding m.ClientHistoryMutexState(inv, client) in *(m.snapshots)[client] == newValue +ensures m.Clients(inv, client) == old(m.ClientsLocked(inv, client, currentValue)) +func (m *ClientHistoryMutex) Unlock(inv ClientHistoryInv, client ClientId, currentValue, newValue ValueType) { + inv.TwoStepValueInvReflexive(newValue) + m.UnlockWithSnapshot(inv, client, currentValue, newValue, newValue) +} + +ghost +decreases +requires m.ClientHistoryMutexStateLocked(inv, client, currentValue) +requires m.GetInvLocked(inv, client, currentValue).TwoStepValueInv(m.LastSeenValueLocked(inv, client, currentValue), currentValue) +requires inv.TwoStepValueInv(currentValue, newValue) +// the following precondition enforces that the global trace can only be extended by up to a single trace entry: +requires newValue == currentValue || tr.getPrev(newValue) == currentValue +requires inv.TwoStepValueInv(snapshot, newValue) +requires inv.CurrentValueInv(newValue) +ensures m.ClientHistoryMutexState(inv, client) +ensures unfolding m.ClientHistoryMutexState(inv, client) in *(m.snapshots)[client] == snapshot +ensures m.Clients(inv, client) == old(m.ClientsLocked(inv, client, currentValue)) +/** + * similar to `Unlock` in the sense that the lock is released. + * however, this method allows to manually specify the snapshot to which this client's lastSeenValue should be set + */ +func (m *ClientHistoryMutex) UnlockWithSnapshot(inv ClientHistoryInv, client ClientId, currentValue, newValue, snapshot ValueType) { + unfold m.ClientHistoryMutexStateLocked(inv, client, currentValue) + m.applyInvTransitivity(inv, range(m.snapshots), currentValue, newValue, 1/2) + *(m.snapshots)[client] = snapshot + // the following assert statement is necessary due to an incompleteness: + assert *(m.snapshots)[client] == snapshot + *m.currentValue = newValue + fold ClientHistoryMutexInv!() + m.mutex.Unlock() + fold m.ClientHistoryMutexState(inv, client) +} + +ghost +decreases +requires noPerm < p && p <= writePerm +requires inv != nil +requires forall snapshot *ValueType :: { inv.TwoStepValueInv(*snapshot, currentValue) } snapshot in snapshots ==> + acc(snapshot, p) && inv.TwoStepValueInv(*snapshot, currentValue) +requires inv.TwoStepValueInv(currentValue, newValue) +ensures forall snapshot *ValueType :: { inv.TwoStepValueInv(*snapshot, newValue) } snapshot in snapshots ==> + acc(snapshot, p) && inv.TwoStepValueInv(*snapshot, newValue) +func (m *ClientHistoryMutex) applyInvTransitivity(inv ClientHistoryInv, snapshots set[*ValueType], currentValue, newValue ValueType, p perm) { + // non-deterministically choose a snapshot + arbitrarySnapshot@ := arb.GetArbTraceEntry() + if &arbitrarySnapshot in snapshots { + m.applyInvTransitivitySingle(inv, &arbitrarySnapshot, currentValue, newValue, p) + assert inv.TwoStepValueInv(arbitrarySnapshot, newValue) + } + // forall introduction: + assert &arbitrarySnapshot in snapshots ==> + inv.TwoStepValueInv(arbitrarySnapshot, newValue) + assume forall snapshot *ValueType :: { inv.TwoStepValueInv(*snapshot, newValue) } snapshot in snapshots ==> + inv.TwoStepValueInv(*snapshot, newValue) +} + +ghost +decreases +requires noPerm < p && p <= writePerm +requires inv != nil +requires acc(snapshot, p) && inv.TwoStepValueInv(*snapshot, currentValue) +requires inv.TwoStepValueInv(currentValue, newValue) +ensures acc(snapshot, p) && inv.TwoStepValueInv(*snapshot, newValue) +func (m *ClientHistoryMutex) applyInvTransitivitySingle(inv ClientHistoryInv, snapshot *ValueType, currentValue, newValue ValueType, p perm) { + inv.TwoStepValueInvTransitive(*snapshot, currentValue, newValue) +} diff --git a/ReusableVerificationLibrary/event/event.gobra b/ReusableVerificationLibrary/event/event.gobra new file mode 100644 index 0000000..b457d9c --- /dev/null +++ b/ReusableVerificationLibrary/event/event.gobra @@ -0,0 +1,21 @@ +package event + + +// we model events by a type and some parameters. +// a protocol implementation will specify the event types relevant +// for the protocol and the concrete parameters for each event type. +type EventParams interface {} + +type EventType int +type Event struct { + typ EventType + params EventParams +} + +ghost +decreases +requires isComparable(params) +ensures res.typ == typ && res.params == params +pure func NewEvent(typ EventType, params EventParams) (res Event) { + return Event{ typ, params } +} diff --git a/ReusableVerificationLibrary/go.mod b/ReusableVerificationLibrary/go.mod new file mode 100644 index 0000000..ab14b92 --- /dev/null +++ b/ReusableVerificationLibrary/go.mod @@ -0,0 +1,5 @@ +module github.com/viperproject/ReusableProtocolVerificationLibrary + +go 1.16 + +require golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa diff --git a/ReusableVerificationLibrary/go.sum b/ReusableVerificationLibrary/go.sum new file mode 100644 index 0000000..0beaab0 --- /dev/null +++ b/ReusableVerificationLibrary/go.sum @@ -0,0 +1,10 @@ +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/ReusableVerificationLibrary/label/secrecy-label.gobra b/ReusableVerificationLibrary/label/secrecy-label.gobra new file mode 100644 index 0000000..78ddfae --- /dev/null +++ b/ReusableVerificationLibrary/label/secrecy-label.gobra @@ -0,0 +1,109 @@ +package label + +import p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" + +type SecrecyLabel domain { + // constructors + // type 0 + func Public() SecrecyLabel + // type 1 + func Readers(set[p.Id]) SecrecyLabel + // type 2 + func Meet(SecrecyLabel, SecrecyLabel) SecrecyLabel // intersection of labels + // type 3 + func Join(SecrecyLabel, SecrecyLabel) SecrecyLabel // union of labels + + // WARNING: adapt first axiom if another SecrecyLabel is added! + + // deconstructors + // TODO: get rid of `GetFirstLabel` and `GetSecondLabel` and express commutativity in the usual way + func GetType(SecrecyLabel) int + func GetHeight(SecrecyLabel) int + func GetReaders(SecrecyLabel) set[p.Id] + func GetFirstLabel(SecrecyLabel) SecrecyLabel + func GetSecondLabel(SecrecyLabel) SecrecyLabel + + axiom { // all labels have a type + forall l SecrecyLabel :: { GetType(l) } 0 <= GetType(l) && GetType(l) <= 3 + } + + axiom { // public has type 0 + GetType(Public()) == 0 && + GetHeight(Public()) == 0 + } + axiom { // public type implies its construction + forall l SecrecyLabel :: { GetType(l) } GetType(l) == 0 ==> l == Public() + } + + axiom { // readers is injective + forall scopes set[p.Id] :: { Readers(scopes) } GetType(Readers(scopes)) == 1 && + GetHeight(Readers(scopes)) == 0 && + GetReaders(Readers(scopes)) == scopes + } + axiom { // readers type implies its construction + forall l SecrecyLabel :: { GetType(l) } GetType(l) == 1 ==> l == Readers(GetReaders(l)) + } + + axiom { // meet is pseudo-injective, i.e. injective modulo commutativity + forall l1, l2 SecrecyLabel :: { Meet(l1, l2) } GetType(Meet(l1, l2)) == 2 && + GetHeight(Meet(l1, l2)) == 1 + (GetHeight(l1) >= GetHeight(l2) ? GetHeight(l1) : GetHeight(l2)) && + (( + GetFirstLabel(Meet(l1, l2)) == l1 && + GetSecondLabel(Meet(l1, l2)) == l2) || + ( + GetFirstLabel(Meet(l1, l2)) == l2 && + GetSecondLabel(Meet(l1, l2)) == l1)) + } + axiom { // meet is commutative + forall l1, l2 SecrecyLabel :: { Meet(l1, l2) } Meet(l1, l2) == Meet(l2, l1) + } + axiom { // meet type implies its construction + forall l SecrecyLabel :: { GetType(l) } GetType(l) == 2 ==> l == Meet(GetFirstLabel(l), GetSecondLabel(l)) + } + + axiom { // join is pseudo-injective, i.e. injective modulo commutativity + forall l1, l2 SecrecyLabel :: { Join(l1, l2) } GetType(Join(l1, l2)) == 3 && + GetHeight(Join(l1, l2)) == 1 + (GetHeight(l1) >= GetHeight(l2) ? GetHeight(l1) : GetHeight(l2)) && + (( + GetFirstLabel(Join(l1, l2)) == l1 && + GetSecondLabel(Join(l1, l2)) == l2) || + ( + GetFirstLabel(Join(l1, l2)) == l2 && + GetSecondLabel(Join(l1, l2)) == l1)) + } + axiom { // join is commutative + forall l1, l2 SecrecyLabel :: { Join(l1, l2) } Join(l1, l2) == Join(l2, l1) + } + axiom { // join type implies its construction + forall l SecrecyLabel :: { GetType(l) } GetType(l) == 3 ==> l == Join(GetFirstLabel(l), GetSecondLabel(l)) + } + + // axioms for GetHeight: + axiom { + forall l SecrecyLabel :: { GetHeight(l) } 0 <= GetHeight(l) + } +} + +ghost +decreases +pure func (label SecrecyLabel) IsPublic() bool { + return GetType(label) == 0 +} + +ghost +decreases +pure func (label SecrecyLabel) IsReaders() bool { + return GetType(label) == 1 +} + +ghost +decreases +pure func (label SecrecyLabel) IsMeet() bool { + return GetType(label) == 2 +} + +ghost +decreases +pure func (label SecrecyLabel) IsJoin() bool { + return GetType(label) == 3 +} diff --git a/ReusableVerificationLibrary/labeledlibrary/channelcommunication/channel-communication.go b/ReusableVerificationLibrary/labeledlibrary/channelcommunication/channel-communication.go new file mode 100644 index 0000000..f0c11d0 --- /dev/null +++ b/ReusableVerificationLibrary/labeledlibrary/channelcommunication/channel-communication.go @@ -0,0 +1,62 @@ +package channelcommunication + +//@ import ll "github.com/viperproject/ReusableProtocolVerificationLibrary/labeledlibrary" +import lib "github.com/viperproject/ReusableProtocolVerificationLibrary/labeledlibrary/library" +import p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" +//@ import tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" +//@ import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" + + +type ChannelCommunicaton struct { + channels map[ChannelKey]chan []byte +} +// the following statement is not necessary but makes subtyping explicit (for documentation purposes) +//@ (* ChannelCommunicaton) implements ll.Communication + +type ChannelKey struct { + idSender p.Principal + idReceiver p.Principal +} + +/*@ +pred (com *ChannelCommunicaton) LibMem() { + acc(com) +} +@*/ + +//@ decreases +//@ ensures res.LibMem() +func NewChannelCommunication(initiator, responder p.Principal) (res *ChannelCommunicaton) { + res = &ChannelCommunicaton{} + res.channels = make(map[ChannelKey]chan []byte) + // create a channel per communication direction: + (res.channels)[ChannelKey{initiator, responder}] = make(chan []byte) + (res.channels)[ChannelKey{responder, initiator}] = make(chan []byte) + //@ fold res.LibMem() + return res +} + +//@ trusted +//@ requires acc(com.LibMem(), 1/16) +//@ requires acc(lib.Mem(msg), 1/16) +//@ requires lib.Abs(msg) == tm.gamma(msgT) +//@ requires snapshot.isMessageAt(idSender, idReceiver, msgT) +//@ ensures acc(com.LibMem(), 1/16) +//@ ensures acc(lib.Mem(msg), 1/16) +func (com *ChannelCommunicaton) Send(idSender, idReceiver p.Principal, msg lib.ByteString /*@, ghost msgT tm.Term, ghost snapshot tr.TraceEntry @*/) error { + channel := (com.channels)[ChannelKey{idSender, idReceiver}] + channel <- msg + return nil +} + +//@ trusted +//@ requires acc(com.LibMem(), 1/16) +//@ ensures acc(com.LibMem(), 1/16) +//@ ensures err == nil ==> lib.Mem(msg) +//@ ensures err == nil ==> lib.Abs(msg) == tm.gamma(msgT) +//@ ensures err == nil ==> snapshot.messageOccurs(idSender, idReceiver, msgT) +func (com *ChannelCommunicaton) Receive(idSender, idReceiver p.Principal /*@, ghost snapshot tr.TraceEntry @*/) (msg lib.ByteString, err error /*@, ghost msgT tm.Term @*/) { + channel := (com.channels)[ChannelKey{idSender, idReceiver}] + msg = <-channel + return msg, nil /*@, tm.oneTerm(msg) @*/ +} diff --git a/ReusableVerificationLibrary/labeledlibrary/crypto.go b/ReusableVerificationLibrary/labeledlibrary/crypto.go new file mode 100644 index 0000000..0e18dec --- /dev/null +++ b/ReusableVerificationLibrary/labeledlibrary/crypto.go @@ -0,0 +1,333 @@ +package labeledlibrary + +import ( + //@ arb "github.com/viperproject/ReusableProtocolVerificationLibrary/arbitrary" + //@ ev "github.com/viperproject/ReusableProtocolVerificationLibrary/event" + //@ "github.com/viperproject/ReusableProtocolVerificationLibrary/label" + //@ "github.com/viperproject/ReusableProtocolVerificationLibrary/labeling" + lib "github.com/viperproject/ReusableProtocolVerificationLibrary/labeledlibrary/library" + //@ p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" + //@ tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" + //@ tri "github.com/viperproject/ReusableProtocolVerificationLibrary/traceinvariant" + //@ u "github.com/viperproject/ReusableProtocolVerificationLibrary/usage" +) + +//@ requires l.Mem() +//@ requires tri.GetLabeling(l.Ctx()).CanFlow(l.Snapshot(), nonceLabel, label.Readers(set[p.Id]{ l.OwnerWoThread() })) +//@ ensures l.Mem() +//@ ensures l.ImmutableState() == old(l.ImmutableState()) +//@ ensures old(l.Snapshot()).isSuffix(l.Snapshot()) +//@ ensures err == nil ==> lib.Mem(nonce) && lib.Size(nonce) == lib.NonceLength +//@ ensures err == nil ==> lib.Abs(nonce) == tm.gamma(tm.random(lib.Abs(nonce), nonceLabel, u.Nonce(usageString))) +//@ ensures err == nil ==> l.Snapshot().isNonceAt(tm.random(lib.Abs(nonce), nonceLabel, u.Nonce(usageString))) +//@ ensures err == nil ==> forall eventType ev.EventType :: { eventType in eventTypes } eventType in eventTypes ==> (l.LabelCtx()).NonceForEventIsUnique(tm.random(lib.Abs(nonce), nonceLabel, u.Nonce(usageString)), eventType) +func (l *LabeledLibrary) CreateNonce(/*@ ghost nonceLabel label.SecrecyLabel, ghost usageString string, ghost eventTypes set[ev.EventType] @*/) (nonce lib.ByteString, err error) { + //@ unfold l.Mem() + nonce, err = l.s.CreateNonce(/*@ tri.GetLabeling(l.ctx), nonceLabel, usageString, eventTypes @*/) + // store nonce on trace + /*@ + ghost if err == nil { + nonceT := tm.random(lib.Abs(nonce), nonceLabel, u.Nonce(usageString)) + l.manager.LogNonce(l.ctx, l.owner, nonceT) + } + @*/ + //@ fold l.Mem() + return +} + +//@ requires l.Mem() +//@ ensures l.Mem() +//@ ensures l.ImmutableState() == old(l.ImmutableState()) +//@ ensures old(l.Snapshot()).isSuffix(l.Snapshot()) +//@ ensures err == nil ==> lib.Mem(pk) +//@ ensures err == nil ==> lib.Mem(sk) +//@ ensures err == nil ==> lib.Abs(sk) == tm.gamma(skT) && lib.Abs(pk) == tm.createPkB(lib.Abs(sk)) +//@ ensures err == nil ==> l.Snapshot().isNonceAt(skT) +//@ ensures err == nil ==> skT == tm.random(lib.Abs(sk), label.Readers(set[p.Id]{ l.OwnerWoThread() }), u.PkeKey(usageString)) +// TODO make skT ghost +func (l *LabeledLibrary) GeneratePkeKey(/*@ ghost usageString string @*/) (pk, sk lib.ByteString, err error /*@, skT tm.Term @*/) { + //@ ownerWoThread := l.OwnerWoThread() + //@ unfold l.Mem() + //@ keyLabel := label.Readers(set[p.Id]{ ownerWoThread }) + pk, sk, err = l.s.GeneratePkeKey(/*@ tri.GetLabeling(l.ctx), keyLabel, usageString, set[ev.EventType]{} @*/) + // store sk on trace + /*@ + ghost if err == nil { + skT = tm.random(lib.Abs(sk), keyLabel, u.PkeKey(usageString)) + tri.GetLabeling(l.ctx).CanFlowReflexive(l.manager.Snapshot(l.ctx, l.owner), keyLabel) + l.manager.LogNonce(l.ctx, l.owner, skT) + } + @*/ + //@ fold l.Mem() + return +} + +//@ requires l.Mem() +//@ ensures l.Mem() +//@ ensures l.ImmutableState() == old(l.ImmutableState()) +//@ ensures old(l.Snapshot()).isSuffix(l.Snapshot()) +//@ ensures err == nil ==> lib.Mem(key) && lib.Size(key) == 32 +//@ ensures err == nil ==> lib.Abs(key) == tm.gamma(skT) +//@ ensures err == nil ==> skT == tm.random(lib.Abs(key), label.Readers(set[p.Id]{ l.OwnerWoThread() }), u.DhKey(usageString)) +//@ ensures err == nil ==> l.Snapshot().isNonceAt(skT) +//@ ensures err == nil ==> forall eventType ev.EventType :: { eventType in eventTypes } eventType in eventTypes ==> l.LabelCtx().NonceForEventIsUnique(skT, eventType) +func (l *LabeledLibrary) GenerateDHKey(/*@ ghost usageString string, ghost eventTypes set[ev.EventType] @*/) (key lib.ByteString, err error /*@, ghost skT tm.Term @*/) { + //@ ownerWoThread := l.OwnerWoThread() + //@ unfold l.Mem() + //@ keyLabel := label.Readers(set[p.Id]{ ownerWoThread }) + key, err = l.s.GenerateDHKey(/*@ tri.GetLabeling(l.ctx), keyLabel, usageString, eventTypes @*/) + // store key on trace + /*@ + ghost if err == nil { + skT = tm.random(lib.Abs(key), keyLabel, u.DhKey(usageString)) + tri.GetLabeling(l.ctx).CanFlowReflexive(l.manager.Snapshot(l.ctx, l.owner), keyLabel) + l.manager.LogNonce(l.ctx, l.owner, skT) + } + @*/ + //@ fold l.Mem() + return +} + +//@ requires l.Mem() +//@ ensures l.Mem() +//@ ensures l.ImmutableState() == old(l.ImmutableState()) +//@ ensures old(l.Snapshot()).isSuffix(l.Snapshot()) +//@ ensures err == nil ==> lib.Mem(pk) +//@ ensures err == nil ==> lib.Mem(sk) +//@ ensures err == nil ==> lib.Abs(sk) == tm.gamma(skT) && lib.Abs(pk) == tm.createPkB(lib.Abs(sk)) +//@ ensures err == nil ==> l.Snapshot().isNonceAt(skT) +//@ ensures err == nil ==> skT == tm.random(lib.Abs(sk), label.Readers(set[p.Id]{ l.OwnerWoThread() }), u.SigningKey(usageString)) +// TODO make skT ghost +func (l *LabeledLibrary) GenerateSigningKey(/*@ ghost usageString string @*/) (pk, sk lib.ByteString, err error /*@, skT tm.Term @*/) { + //@ ownerWoThread := l.OwnerWoThread() + //@ unfold l.Mem() + //@ keyLabel := label.Readers(set[p.Id]{ ownerWoThread }) + pk, sk, err = l.s.GenerateSigningKey(/*@ tri.GetLabeling(l.ctx), keyLabel, usageString, set[ev.EventType]{} @*/) + // store sk on trace + /*@ + ghost if err == nil { + skT = tm.random(lib.Abs(sk), keyLabel, u.SigningKey(usageString)) + tri.GetLabeling(l.ctx).CanFlowReflexive(l.manager.Snapshot(l.ctx, l.owner), keyLabel) + l.manager.LogNonce(l.ctx, l.owner, skT) + } + @*/ + //@ fold l.Mem() + return +} + +//@ requires l.Mem() +//@ requires acc(lib.Mem(msg), 1/8) +//@ requires lib.Abs(msg) == tm.gamma(msgT) +//@ requires acc(lib.Mem(pk), 1/8) +//@ requires lib.Abs(pk) == tm.gamma(pkT) +//@ requires l.LabelCtx().CanEncrypt(l.Snapshot(), msgT, pkT) || (l.LabelCtx().IsPublishable(l.Snapshot(), msgT) && l.LabelCtx().IsPublishable(l.Snapshot(), pkT)) +//@ ensures l.Mem() +//@ ensures l.ImmutableState() == old(l.ImmutableState()) +//@ ensures l.Snapshot() == old(l.Snapshot()) +//@ ensures acc(lib.Mem(msg), 1/8) +//@ ensures acc(lib.Mem(pk), 1/8) +//@ ensures err == nil ==> lib.Mem(ciphertext) +//@ ensures err == nil ==> lib.Abs(ciphertext) == tm.encryptB(lib.Abs(msg), lib.Abs(pk)) +//@ ensures err == nil ==> l.LabelCtx().IsPublishable(l.Snapshot(), tm.encrypt(msgT, pkT)) +func (l *LabeledLibrary) Enc(msg, pk lib.ByteString /*@, ghost msgT tm.Term, ghost pkT tm.Term @*/) (ciphertext lib.ByteString, err error) { + //@ unfold l.Mem() + ciphertext, err = l.s.Enc(msg, pk) + //@ fold l.Mem() + //@ l.LabelCtx().CiphertextIsPublishable(l.Snapshot(), msgT, pkT) + return +} + +//@ requires l.Mem() +//@ requires acc(lib.Mem(ciphertext), 1/8) +//@ requires lib.Abs(ciphertext) == tm.gamma(ciphertextT) +//@ requires acc(lib.Mem(sk), 1/8) +//@ requires lib.Abs(sk) == tm.gamma(skT) +//@ requires l.LabelCtx().CanDecrypt(l.Snapshot(), ciphertextT, skT, skOwner) +//@ ensures l.Mem() +//@ ensures l.ImmutableState() == old(l.ImmutableState()) +//@ ensures l.Snapshot() == old(l.Snapshot()) +//@ ensures acc(lib.Mem(ciphertext), 1/8) +//@ ensures acc(lib.Mem(sk), 1/8) +//@ ensures err == nil ==> lib.Mem(msg) +//@ ensures err == nil ==> lib.Abs(ciphertext) == tm.encryptB(lib.Abs(msg), tm.createPkB(lib.Abs(sk))) +//@ ensures err == nil ==> (forall msgT tm.Term :: { tm.encrypt(msgT, tm.createPk(skT)) } ciphertextT == tm.encrypt(msgT, tm.createPk(skT)) ==> +//@ l.LabelCtx().WasDecrypted(l.Snapshot(), msgT, skT, skOwner)) +func (l *LabeledLibrary) Dec(ciphertext, sk lib.ByteString /*@, ghost ciphertextT tm.Term, ghost skT tm.Term, ghost skOwner p.Id @*/) (msg lib.ByteString, err error) { + //@ unfold l.Mem() + msg, err = l.s.Dec(ciphertext, sk) + //@ fold l.Mem() + /*@ + ghost if err == nil { + pkT := tm.createPk(skT) + + // we choose an arbitrary msgT and then show that if we assume that it's the correct + // we can call `DecryptSatisfiesInvariant` which then gives us an implication with the given quantifier + arbMsgT := arb.GetArbTerm() + if ciphertextT == tm.encrypt(arbMsgT, pkT) { + l.LabelCtx().DecryptSatisfiesInvariant(l.Snapshot(), arbMsgT, skT, skOwner) + } + // forall introduction: + assert ciphertextT == tm.encrypt(arbMsgT, pkT) ==> l.LabelCtx().WasDecrypted(l.Snapshot(), arbMsgT, skT, skOwner) + assume forall msgT tm.Term :: { tm.encrypt(msgT, pkT) } ciphertextT == tm.encrypt(msgT, pkT) ==> l.LabelCtx().WasDecrypted(l.Snapshot(), msgT, skT, skOwner) + } + @*/ + return +} + +//@ requires l.Mem() +//@ requires acc(lib.Mem(key), 1/16) && acc(lib.Mem(nonce), 1/16) +//@ requires lib.Size(key) == 32 && lib.Size(nonce) == 12 +//@ requires plaintext != nil ==> acc(lib.Mem(plaintext), 1/16) +//@ requires additionalData != nil ==> acc(lib.Mem(additionalData), 1/16) +//@ requires lib.Abs(key) == tm.gamma(keyT) +//@ requires lib.Abs(nonce) == tm.gamma(nonceT) +//@ requires lib.SafeAbs(plaintext, 0) == tm.gamma(plaintextT) +//@ requires lib.SafeAbs(additionalData, 0) == tm.gamma(adT) +//@ requires l.LabelCtx().CanAeadEncrypt(l.Snapshot(), keyT, nonceT, plaintextT, adT, keyL) || (l.LabelCtx().IsPublishable(l.Snapshot(), keyT) && l.LabelCtx().IsPublishable(l.Snapshot(), nonceT) && l.LabelCtx().IsPublishable(l.Snapshot(), plaintextT) && l.LabelCtx().IsPublishable(l.Snapshot(), adT)) +//@ ensures l.Mem() +//@ ensures l.ImmutableState() == old(l.ImmutableState()) +//@ ensures l.Snapshot() == old(l.Snapshot()) +//@ ensures acc(lib.Mem(key), 1/16) && acc(lib.Mem(nonce), 1/16) +//@ ensures plaintext != nil ==> acc(lib.Mem(plaintext), 1/16) +//@ ensures additionalData != nil ==> acc(lib.Mem(additionalData), 1/16) +//@ ensures err == nil ==> lib.Mem(ciphertext) && lib.Size(ciphertext) == (plaintext == nil ? 0 : lib.Size(plaintext)) + 16 +//@ ensures err == nil ==> lib.Abs(ciphertext) == tm.aeadB(lib.Abs(key), lib.Abs(nonce), lib.SafeAbs(plaintext, 0), lib.SafeAbs(additionalData, 0)) +//@ ensures err == nil ==> l.LabelCtx().IsPublishable(l.Snapshot(), tm.aead(keyT, nonceT, plaintextT, adT)) +func (l *LabeledLibrary) AeadEnc(key, nonce, plaintext, additionalData lib.ByteString /*@, ghost keyT tm.Term, ghost nonceT tm.Term, ghost plaintextT tm.Term, ghost adT tm.Term, ghost keyL label.SecrecyLabel @*/) (ciphertext lib.ByteString, err error) { + //@ unfold l.Mem() + ciphertext, err = l.s.AeadEnc(key, nonce, plaintext, additionalData) + //@ fold l.Mem() + //@ l.LabelCtx().AeadCiphertextIsPublishable(l.Snapshot(), keyT, nonceT, plaintextT, adT, keyL) + return +} + +//@ requires l.Mem() +//@ requires acc(lib.Mem(key), 1/16) && acc(lib.Mem(nonce), 1/16) +//@ requires lib.Size(key) == 32 && lib.Size(nonce) == 12 +//@ requires acc(lib.Mem(ciphertext), 1/16) +//@ requires additionalData != nil ==> acc(lib.Mem(additionalData), 1/16) +//@ requires lib.Abs(key) == tm.gamma(keyT) +//@ requires lib.Abs(nonce) == tm.gamma(nonceT) +//@ requires lib.Abs(ciphertext) == tm.gamma(ciphertextT) +//@ requires lib.SafeAbs(additionalData, 0) == tm.gamma(adT) +//@ requires l.LabelCtx().CanAeadDecrypt(l.Snapshot(), keyT, nonceT, ciphertextT, adT, keyL) +//@ ensures l.Mem() +//@ ensures l.ImmutableState() == old(l.ImmutableState()) +//@ ensures l.Snapshot() == old(l.Snapshot()) +//@ ensures acc(lib.Mem(key), 1/16) && acc(lib.Mem(nonce), 1/16) && acc(lib.Mem(ciphertext), 1/16) +//@ ensures additionalData != nil ==> acc(lib.Mem(additionalData), 1/16) +//@ ensures err == nil ==> lib.Mem(res) && lib.Size(res) == lib.Size(ciphertext) - 16 +//@ ensures err == nil ==> lib.Abs(ciphertext) == tm.aeadB(lib.Abs(key), lib.Abs(nonce), lib.Abs(res), lib.SafeAbs(additionalData, 0)) +//@ ensures err == nil ==> (forall msgT tm.Term :: { tm.aead(keyT, nonceT, msgT, adT) } ciphertextT == tm.aead(keyT, nonceT, msgT, adT) ==> +//@ l.LabelCtx().WasAeadDecrypted(l.Snapshot(), keyT, nonceT, msgT, adT, keyL)) +func (l *LabeledLibrary) AeadDec(key, nonce, ciphertext, additionalData lib.ByteString /*@, ghost keyT tm.Term, ghost nonceT tm.Term, ghost ciphertextT tm.Term, ghost adT tm.Term, ghost keyL label.SecrecyLabel @*/) (res lib.ByteString, err error) { + //@ unfold l.Mem() + res, err = l.s.AeadDec(key, nonce, ciphertext, additionalData) + //@ fold l.Mem() + /*@ + ghost if err == nil { + // we choose an arbitrary msgT and then show that if we assume that it's the correct + // we can call `AeadDecryptSatisfiesInvariant` which then gives us an implication with the given quantifier + arbMsgT := arb.GetArbTerm() + if ciphertextT == tm.aead(keyT, nonceT, arbMsgT, adT) { + l.LabelCtx().AeadDecryptSatisfiesInvariant(l.Snapshot(), keyT, nonceT, arbMsgT, adT, keyL) + } + // forall introduction: + assert ciphertextT == tm.aead(keyT, nonceT, arbMsgT, adT) ==> l.LabelCtx().WasAeadDecrypted(l.Snapshot(), keyT, nonceT, arbMsgT, adT, keyL) + assume forall msgT tm.Term :: { tm.aead(keyT, nonceT, msgT, adT) } ciphertextT == tm.aead(keyT, nonceT, msgT, adT) ==> l.LabelCtx().WasAeadDecrypted(l.Snapshot(), keyT, nonceT, msgT, adT, keyL) + } + @*/ + return +} + +//@ requires l.Mem() +//@ requires acc(lib.Mem(msg), 1/8) +//@ requires lib.Abs(msg) == tm.gamma(msgT) +//@ requires acc(lib.Mem(sk), 1/8) +//@ requires lib.Abs(sk) == tm.gamma(skT) +//@ requires l.LabelCtx().CanSign(l.Snapshot(), msgT, skT, skOwner) || (l.LabelCtx().IsPublishable(l.Snapshot(), msgT) && l.LabelCtx().IsPublishable(l.Snapshot(), skT)) +//@ ensures l.Mem() +//@ ensures l.ImmutableState() == old(l.ImmutableState()) +//@ ensures l.Snapshot() == old(l.Snapshot()) +//@ ensures acc(lib.Mem(msg), 1/8) +//@ ensures acc(lib.Mem(sk), 1/8) +//@ ensures err == nil ==> lib.Mem(signedMsg) +//@ ensures err == nil ==> lib.Abs(signedMsg) == tm.signB(lib.Abs(msg), lib.Abs(sk)) +//@ ensures err == nil ==> l.LabelCtx().IsPublishable(l.Snapshot(), tm.sign(msgT, skT)) +func (l *LabeledLibrary) Sign(msg, sk lib.ByteString /*@, ghost msgT tm.Term, ghost skT tm.Term, ghost skOwner p.Id @*/) (signedMsg lib.ByteString, err error) { + //@ unfold l.Mem() + signedMsg, err = l.s.Sign(msg, sk) + //@ fold l.Mem() + //@ l.LabelCtx().SignedMessageIsPublishable(l.Snapshot(), msgT, skT, skOwner) + return +} + +//@ requires l.Mem() +//@ requires acc(lib.Mem(signedMsg), 1/8) +//@ requires lib.Abs(signedMsg) == tm.gamma(signedMsgT) +//@ requires acc(lib.Mem(pk), 1/8) +//@ requires lib.Abs(pk) == tm.gamma(tm.createPk(skT)) +//@ requires l.LabelCtx().CanOpen(l.Snapshot(), signedMsgT, tm.createPk(skT), skOwner) +//@ ensures l.Mem() +//@ ensures l.ImmutableState() == old(l.ImmutableState()) +//@ ensures l.Snapshot() == old(l.Snapshot()) +//@ ensures acc(lib.Mem(signedMsg), 1/8) +//@ ensures acc(lib.Mem(pk), 1/8) +//@ ensures err == nil ==> lib.Mem(msg) +//@ ensures err == nil ==> lib.Abs(signedMsg) == tm.signB(lib.Abs(msg), tm.gamma(skT)) +//@ ensures err == nil ==> (forall msgT tm.Term :: { tm.sign(msgT, skT) } signedMsgT == tm.sign(msgT, skT) ==> +//@ l.LabelCtx().WasOpened(l.Snapshot(), msgT, skT, skOwner)) +func (l *LabeledLibrary) Open(signedMsg, pk lib.ByteString /*@, ghost signedMsgT tm.Term, ghost skT tm.Term, ghost skOwner p.Id @*/) (msg lib.ByteString, err error) { + //@ unfold l.Mem() + msg, err = l.s.Open(signedMsg, pk /*@, skT @*/) + //@ fold l.Mem() + /*@ + ghost if err == nil { + // we choose an arbitrary msgT and then show that if we assume that it's the correct + // we can call `OpenSatisfiesInvariant` which then gives us an implication with the given quantifier + arbMsgT := arb.GetArbTerm() + if signedMsgT == tm.sign(arbMsgT, skT) { + l.LabelCtx().OpenSatisfiesInvariant(l.Snapshot(), arbMsgT, skT, skOwner) + } + // forall introduction: + assert signedMsgT == tm.sign(arbMsgT, skT) ==> l.LabelCtx().WasOpened(l.Snapshot(), arbMsgT, skT, skOwner) + assume forall msgT tm.Term :: { tm.sign(msgT, skT) } signedMsgT == tm.sign(msgT, skT) ==> l.LabelCtx().WasOpened(l.Snapshot(), msgT, skT, skOwner) + } + @*/ + return +} + +//@ requires l.Mem() +//@ requires acc(lib.Mem(exp), 1/16) +//@ requires lib.Abs(exp) == tm.gamma(expT) +//@ requires l.LabelCtx().IsValid(l.Snapshot(), expT) && expT.IsRandom() +//@ ensures l.Mem() +//@ ensures acc(lib.Mem(exp), 1/16) +//@ ensures l.ImmutableState() == old(l.ImmutableState()) +//@ ensures l.Snapshot() == old(l.Snapshot()) +//@ ensures l.LabelCtx().IsPublishable(l.Snapshot(), tm.exp(tm.generator(), expT)) +//@ ensures err == nil ==> lib.Mem(res) +//@ ensures err == nil ==> lib.Abs(res) == tm.expB(tm.generatorB(), lib.Abs(exp)) +// arg is big-endian +func (l *LabeledLibrary) DhExp(exp []byte /*@, ghost expT tm.Term @*/) (res []byte, err error) { + //@ unfold l.Mem() + res, err = l.s.DhExp(exp) + //@ fold l.Mem() + // the following assert stmt is necessary to derive publishability of `res`: + //@ assert l.LabelCtx().IsValid(l.Snapshot(), tm.generator()) + return +} + +//@ preserves l.Mem() +//@ preserves acc(lib.Mem(dhSecret), 1/16) && acc(lib.Mem(dhHalfKey), 1/16) +//@ ensures l.ImmutableState() == old(l.ImmutableState()) +//@ ensures l.Snapshot() == old(l.Snapshot()) +//@ ensures err == nil ==> lib.Mem(res) +//@ ensures err == nil ==> lib.Abs(res) == tm.expB(lib.Abs(dhHalfKey), lib.Abs(dhSecret)) +// args are big-endian +func (l *LabeledLibrary) DhSharedSecret(dhSecret, dhHalfKey []byte) (res []byte, err error) { + //@ unfold l.Mem() + res, err = l.s.DhSharedSecret(dhSecret, dhHalfKey) + //@ fold l.Mem() + return +} diff --git a/ReusableVerificationLibrary/labeledlibrary/io.go b/ReusableVerificationLibrary/labeledlibrary/io.go new file mode 100644 index 0000000..78382a0 --- /dev/null +++ b/ReusableVerificationLibrary/labeledlibrary/io.go @@ -0,0 +1,100 @@ +package labeledlibrary + +import ( + //@ att "github.com/viperproject/ReusableProtocolVerificationLibrary/attacker" + //@ "github.com/viperproject/ReusableProtocolVerificationLibrary/labeling" + lib "github.com/viperproject/ReusableProtocolVerificationLibrary/labeledlibrary/library" + p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" + //@ tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" + //@ tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" + //@ tri "github.com/viperproject/ReusableProtocolVerificationLibrary/traceinvariant" +) + + +/** abstracts over different communication channels */ +type Communication interface { + //@ pred LibMem() + + //@ requires acc(LibMem(), 1/16) + //@ requires acc(lib.Mem(msg), 1/16) + //@ requires lib.Abs(msg) == tm.gamma(msgT) + //@ requires snapshot.isMessageAt(idSender, idReceiver, msgT) + //@ ensures acc(LibMem(), 1/16) + //@ ensures acc(lib.Mem(msg), 1/16) + Send(idSender, idReceiver p.Principal, msg lib.ByteString /*@, ghost msgT tm.Term, ghost snapshot tr.TraceEntry @*/) error + + //@ requires acc(LibMem(), 1/16) + //@ ensures acc(LibMem(), 1/16) + //@ ensures err == nil ==> lib.Mem(msg) + //@ ensures err == nil ==> lib.Abs(msg) == tm.gamma(msgT) + //@ ensures err == nil ==> snapshot.messageOccurs(idSender, idReceiver, msgT) + // returns a message that was at or before `snapshot`. It's thus adviceable to synchronize to the globa + // trace first such that the set of receivable messages is as big as possible + Receive(idSender, idReceiver p.Principal /*@, ghost snapshot tr.TraceEntry @*/) (msg lib.ByteString, err error /*@, ghost msgT tm.Term @*/) +} + + +/** + * acts as a middleware between participant implementation and the library: + * it not only delegates the call to the library but also creates a corresponding + * trace trace + */ +//@ requires l.Mem() +//@ requires l.Owner().getPrincipal() == idSender +//@ requires acc(lib.Mem(msg), 1/16) +//@ requires tm.gamma(msgT) == lib.Abs(msg) +//@ requires tri.messageInv(l.Ctx(), idSender, idReceiver, msgT, l.Snapshot()) +//@ ensures l.Mem() +//@ ensures l.ImmutableState() == old(l.ImmutableState()) +//@ ensures old(l.Snapshot()).isSuffix(l.Snapshot()) +//@ ensures acc(lib.Mem(msg), 1/16) +//@ ensures err == nil ==> (l.Snapshot()).isMessageAt(idSender, idReceiver, msgT) +func (l *LabeledLibrary) Send(idSender, idReceiver p.Principal, msg lib.ByteString /*@, ghost msgT tm.Term @*/) (err error) { + //@ unfold l.Mem() + //@ l.manager.LogSend(l.ctx, l.owner, l.owner.getPrincipal(), idReceiver, msgT) + //@ snapshot := l.manager.Snapshot(l.ctx, l.owner) + err = l.com.Send(idSender, idReceiver, msg /*@, msgT, snapshot @*/) + //@ fold l.Mem() + return +} + +//@ requires l.Mem() +//@ requires l.Owner().getPrincipal() == idReceiver +//@ ensures l.Mem() +//@ ensures l.ImmutableState() == old(l.ImmutableState()) +//@ ensures old(l.Snapshot()).isSuffix(l.Snapshot()) +//@ ensures err == nil ==> lib.Mem(msg) +//@ ensures err == nil ==> lib.Abs(msg) == tm.gamma(msgT) +//@ ensures err == nil ==> tri.messageInv(l.Ctx(), idSender, idReceiver, msgT, l.Snapshot()) +//@ ensures err == nil ==> (l.Snapshot()).messageOccurs(idSender, idReceiver, msgT) +func (l *LabeledLibrary) Receive(idSender, idReceiver p.Principal) (msg lib.ByteString, err error /*@, ghost msgT tm.Term @*/) { + //@ unfold l.Mem() + // we first synchronize the local snapshot to the global trace: + //@ snapshot := l.manager.Sync(l.ctx, l.owner) + // and now get a message that was sent up until now: + msg, err /*@, msgT @*/ = l.com.Receive(idSender, idReceiver /*@, snapshot @*/) + //@ fold l.Mem() + /*@ + ghost if err == nil { + prev := l.MessageOccursImpliesMessageInv(idSender, idReceiver, msgT) + (tr.getPrev(prev)).isSuffixTransitive(prev, l.Snapshot()) + tri.messageInvTransitive(l.Ctx(), idSender, idReceiver, msgT, tr.getPrev(prev), l.Snapshot()) + } + @*/ + return +} + +/*@ +ghost +requires l.Mem() +requires (l.LabelCtx()).IsPublishable(l.Snapshot(), term) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures old(l.Snapshot()).isSuffix(l.Snapshot()) +ensures att.attackerKnows(l.Snapshot(), term) +func (l *LabeledLibrary) Publish(term tm.Term) { + unfold l.Mem() + l.manager.LogPublish(l.ctx, l.owner, term) + fold l.Mem() +} +@*/ diff --git a/ReusableVerificationLibrary/labeledlibrary/library/crypto.go b/ReusableVerificationLibrary/labeledlibrary/library/crypto.go new file mode 100644 index 0000000..3c40b89 --- /dev/null +++ b/ReusableVerificationLibrary/labeledlibrary/library/crypto.go @@ -0,0 +1,341 @@ +package library + +import ( + rand "crypto/rand" + rsa "crypto/rsa" + x509 "crypto/x509" + sha256 "crypto/sha256" + "errors" + hex "encoding/hex" + big "math/big" + chacha20poly1305 "golang.org/x/crypto/chacha20poly1305" + sign "golang.org/x/crypto/nacl/sign" + io "io" + //@ ev "github.com/viperproject/ReusableProtocolVerificationLibrary/event" + //@ "github.com/viperproject/ReusableProtocolVerificationLibrary/label" + //@ "github.com/viperproject/ReusableProtocolVerificationLibrary/labeling" + p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" + //@ tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" + //@ tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" + //@ u "github.com/viperproject/ReusableProtocolVerificationLibrary/usage" +) + + +type ByteString []byte + +// wraps IO calls and crypto + +// number of bytes that should be used for nonces +const NonceLength = 24 + +// based on RFC 3526 +const GroupGenerator = 2 +const GroupSizeString = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF" +const DHHalfKeyLength = 256 + + +type LibraryState struct { + // we need at least a field to not run into unknown equality issues + dummy int +} + +/*@ +pred (l *LibraryState) Mem() { + acc(l) +} +@*/ + +//@ decreases +//@ ensures res.Mem() +func NewLibrary(initiator, responder p.Principal) (res *LibraryState) { + res = &LibraryState{} + //@ fold res.Mem() + return res +} + +/*@ +pred Mem(s ByteString) // { + // forall i int :: 0 <= i && i < len(s) ==> acc(&s[i]) +// } + +ghost +requires acc(Mem(b), _) +ensures Size(b) == 0 ==> res == tm.zeroStringB(0) +pure func Abs(b ByteString) (res tm.Bytes) + +ghost +ensures Mem(res) && Abs(res) == bytes +// allocates a new slice of bytes and sets the elements according to `bytes` +func NewByteStringWithContent(bytes tm.Bytes) (res ByteString) + +ghost +requires b != nil ==> acc(Mem(b), _) +ensures b != nil ? res == Abs(b) : res == tm.zeroStringB(l) +pure func SafeAbs(b ByteString, l int) (res tm.Bytes) + +// abstract resource to mark nonces as such +pred IsNonce(b tm.Bytes) +@*/ + +//@ requires acc(Mem(b), _) +//@ ensures res >= 0 && res == len(b) +//@ pure +func Size(b ByteString) (res int) { + return len(b) +} + +//@ trusted +//@ preserves acc(l.Mem(), 1/16) +//@ ensures err == nil ==> Mem(pk) && Mem(sk) +//@ ensures err == nil ==> Abs(pk) == tm.createPkB(Abs(sk)) && Abs(sk) == tm.gamma(tm.random(Abs(sk), keyLabel, u.PkeKey(usageString))) +//@ ensures err == nil ==> ctx.NonceIsUnique(tm.random(Abs(sk), keyLabel, u.PkeKey(usageString))) +//@ ensures err == nil ==> forall eventType ev.EventType :: { eventType in eventTypes } eventType in eventTypes ==> ctx.NonceForEventIsUnique(tm.random(Abs(sk), keyLabel, u.PkeKey(usageString)), eventType) +func (l *LibraryState) GeneratePkeKey(/*@ ghost ctx labeling.LabelingContext, ghost keyLabel label.SecrecyLabel, ghost usageString string, ghost eventTypes set[ev.EventType] @*/) (pk, sk ByteString, err error) { + privateKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return + } + publicKey := privateKey.Public() + + // we serialize the private and public key as PKCS #1, ASN.1 DER and PKIX, ASN.1 DER, respectively. + sk = x509.MarshalPKCS1PrivateKey(privateKey) + + pk, err = x509.MarshalPKIXPublicKey(publicKey) + return +} + +//@ trusted +//@ preserves acc(l.Mem(), 1/16) +//@ ensures err == nil ==> Mem(key) && Size(key) == 32 +//@ ensures err == nil ==> Abs(key) == tm.gamma(tm.random(Abs(key), keyLabel, u.DhKey(usageString))) +//@ ensures err == nil ==> ctx.NonceIsUnique(tm.random(Abs(key), keyLabel, u.DhKey(usageString))) +//@ ensures err == nil ==> forall eventType ev.EventType :: { eventType in eventTypes } eventType in eventTypes ==> ctx.NonceForEventIsUnique(tm.random(Abs(key), keyLabel, u.DhKey(usageString)), eventType) +func (l *LibraryState) GenerateDHKey(/*@ ghost ctx labeling.LabelingContext, ghost keyLabel label.SecrecyLabel, ghost usageString string, ghost eventTypes set[ev.EventType] @*/) (key ByteString, err error) { + var keyBuf [32]byte + key = keyBuf[:] + _, err = rand.Read(key) + if err != nil { + return + } + // clamp + key[0] &= 248 + key[31] = (key[31] & 127) | 64 + return +} + +//@ trusted +//@ preserves acc(l.Mem(), 1/16) +//@ ensures err == nil ==> Mem(pk) && Mem(sk) +//@ ensures err == nil ==> Abs(pk) == tm.createPkB(Abs(sk)) && Abs(sk) == tm.gamma(tm.random(Abs(sk), keyLabel, u.SigningKey(usageString))) +//@ ensures err == nil ==> ctx.NonceIsUnique(tm.random(Abs(sk), keyLabel, u.SigningKey(usageString))) +//@ ensures err == nil ==> forall eventType ev.EventType :: { eventType in eventTypes } eventType in eventTypes ==> ctx.NonceForEventIsUnique(tm.random(Abs(sk), keyLabel, u.SigningKey(usageString)), eventType) +func (l *LibraryState) GenerateSigningKey(/*@ ghost ctx labeling.LabelingContext, ghost keyLabel label.SecrecyLabel, ghost usageString string, ghost eventTypes set[ev.EventType] @*/) (pk, sk ByteString, err error) { + publicKey, privateKey, err := sign.GenerateKey(rand.Reader) + if err != nil { + return + } + + pk = (*publicKey)[:] + sk = (*privateKey)[:] + return +} + +//@ trusted +//@ preserves acc(l.Mem(), 1/16) +//@ ensures err == nil ==> Mem(nonce) && Size(nonce) == NonceLength +//@ ensures err == nil ==> Abs(nonce) == tm.gamma(tm.random(Abs(nonce), nonceLabel, u.Nonce(usageString))) +//@ ensures err == nil ==> ctx.NonceIsUnique(tm.random(Abs(nonce), nonceLabel, u.Nonce(usageString))) +//@ ensures err == nil ==> forall eventType ev.EventType :: { eventType in eventTypes } eventType in eventTypes ==> ctx.NonceForEventIsUnique(tm.random(Abs(nonce), nonceLabel, u.Nonce(usageString)), eventType) +func (l *LibraryState) CreateNonce(/*@ ghost ctx labeling.LabelingContext, ghost nonceLabel label.SecrecyLabel, ghost usageString string, ghost eventTypes set[ev.EventType] @*/) (nonce ByteString, err error) { + var nonceArr [NonceLength]byte + nonce = nonceArr[:] + io.ReadFull(rand.Reader, nonce) + // inhale `NonceIsUnique` and `NonceForEventIsUnique` instances + return nonce, nil +} + +//@ trusted +//@ preserves acc(l.Mem(), 1/16) +//@ preserves acc(Mem(msg), 1/16) +//@ preserves acc(Mem(pk), 1/16) +//@ ensures err == nil ==> Mem(ciphertext) +//@ ensures err == nil ==> Abs(ciphertext) == tm.encryptB(Abs(msg), Abs(pk)) +func (l *LibraryState) Enc(msg, pk ByteString) (ciphertext ByteString, err error) { + // unmarshal pk: + publicKey, err := x509.ParsePKIXPublicKey(pk) + if err != nil { + return + } + + var rsaPublicKey *rsa.PublicKey + switch publicKey := publicKey.(type) { + case *rsa.PublicKey: + rsaPublicKey = publicKey + break + default: + err = errors.New("invalid public key") + return + } + + rng := rand.Reader + ciphertext, err = rsa.EncryptOAEP(sha256.New(), rng, rsaPublicKey, msg, nil) + return +} + +//@ trusted +//@ preserves acc(l.Mem(), 1/16) +//@ preserves acc(Mem(ciphertext), 1/16) +//@ preserves acc(Mem(sk), 1/16) +//@ ensures err == nil ==> Mem(msg) +//@ ensures err == nil ==> Abs(ciphertext) == tm.encryptB(Abs(msg), tm.createPkB(Abs(sk))) +func (l *LibraryState) Dec(ciphertext, sk ByteString) (msg ByteString, err error) { + // unmarshal sk: + privateKey, err := x509.ParsePKCS1PrivateKey(sk) + if err != nil { + return + } + + rng := rand.Reader + msg, err = rsa.DecryptOAEP(sha256.New(), rng, privateKey, ciphertext, nil) + return +} + +//@ trusted +//@ preserves acc(l.Mem(), 1/16) +//@ requires acc(Mem(key), 1/16) && acc(Mem(nonce), 1/16) +//@ requires Size(key) == 32 && Size(nonce) == 12 +//@ preserves plaintext != nil ==> acc(Mem(plaintext), 1/16) +//@ preserves additionalData != nil ==> acc(Mem(additionalData), 1/16) +//@ ensures acc(Mem(key), 1/16) && acc(Mem(nonce), 1/16) +//@ ensures err == nil ==> Mem(ciphertext) && Size(ciphertext) == (plaintext == nil ? 0 : Size(plaintext)) + 16 +//@ ensures err == nil ==> Abs(ciphertext) == tm.aeadB(Abs(key), Abs(nonce), SafeAbs(plaintext, 0), SafeAbs(additionalData, 0)) +func (l *LibraryState) AeadEnc(key, nonce, plaintext, additionalData ByteString) (ciphertext ByteString, err error) { + aead, err := chacha20poly1305.New(key) + if err != nil { + return + } + ciphertext = make([]byte, len(plaintext)+16) + aead.Seal(ciphertext[:0], nonce, plaintext, additionalData) + return +} + +//@ trusted +//@ preserves acc(l.Mem(), 1/16) +//@ requires acc(Mem(key), 1/16) && acc(Mem(nonce), 1/16) +//@ requires Size(key) == 32 && Size(nonce) == 12 +//@ preserves acc(Mem(ciphertext), 1/16) +//@ preserves additionalData != nil ==> acc(Mem(additionalData), 1/16) +//@ ensures acc(Mem(key), 1/16) && acc(Mem(nonce), 1/16) +//@ ensures err == nil ==> Mem(res) && Size(res) == Size(ciphertext) - 16 +//@ ensures err == nil ==> Abs(ciphertext) == tm.aeadB(Abs(key), Abs(nonce), Abs(res), SafeAbs(additionalData, 0)) +func (l *LibraryState) AeadDec(key, nonce, ciphertext, additionalData ByteString) (res ByteString, err error) { + aead, err := chacha20poly1305.New(key) + if err != nil { + return + } + res = make([]byte, len(ciphertext)-16) + _, err = aead.Open(res[:0], nonce, ciphertext, additionalData) + return +} + +//@ trusted +//@ preserves acc(l.Mem(), 1/16) +//@ preserves acc(Mem(data), 1/16) && acc(Mem(sk), 1/16) +//@ ensures err == nil ==> Mem(res) +//@ ensures err == nil ==> Abs(res) == tm.signB(Abs(data), Abs(sk)) +func (l *LibraryState) Sign(data []byte, sk []byte) (res []byte, err error) { + if len(sk) != 64 { + return nil, errors.New("invalid secret key") + } + var skBuf [64]byte + copy(skBuf[:], sk) + + var out []byte + // not that the (64 bytes) signature is prepended to the plaintext + return sign.Sign(out, data, &skBuf), nil +} + +//@ trusted +//@ preserves acc(l.Mem(), 1/16) +//@ preserves acc(Mem(signedData), 1/16) +//@ requires acc(Mem(pk), 1/16) +//@ requires Abs(pk) == tm.gamma(tm.createPk(skT)) +//@ ensures acc(Mem(pk), 1/16) +//@ ensures err == nil ==> Mem(res) +//@ ensures err == nil ==> Abs(signedData) == tm.signB(Abs(res), tm.gamma(skT)) +func (l *LibraryState) Open(signedData []byte, pk []byte /*@, ghost skT tm.Term @*/) (res []byte, err error) { + if len(pk) != 32 { + return nil, errors.New("invalid public key") + } + var pkBuf [32]byte + copy(pkBuf[:], pk) + + var out []byte + data, success := sign.Open(out, signedData, &pkBuf) + if success { + return data, nil + } else { + return nil, errors.New("signature check has failed") + } +} + +//@ trusted +//@ preserves acc(l.Mem(), 1/16) +//@ preserves acc(Mem(exp), 1/16) +//@ ensures err == nil ==> Mem(res) +// arg is big-endian +func (l *LibraryState) expModWithIntBase(base *big.Int, exp []byte) (res []byte, err error) { + // prepare mod argument: + groupSizeBytes, err := hex.DecodeString(GroupSizeString) + if err != nil { + return nil, err + } + mod := big.NewInt(0) + mod.SetBytes(groupSizeBytes) + + // prepare exp argument: + expInt := big.NewInt(0) + expInt.SetBytes(exp) + + // perform calculation: + r := big.NewInt(0) + r.Exp(base, expInt, mod) + + // extract result: + var resBuf [DHHalfKeyLength]byte + r.FillBytes(resBuf[:]) + return resBuf[:], nil +} + +//@ trusted +//@ preserves acc(l.Mem(), 1/16) +//@ preserves acc(Mem(base), 1/16) && acc(Mem(exp), 1/16) +//@ ensures err == nil ==> Mem(res) +// args are big-endian +func (l *LibraryState) expMod(base, exp []byte) (res []byte, err error) { + // prepare mod argument: + baseInt := big.NewInt(0) + baseInt.SetBytes(base) + return l.expModWithIntBase(baseInt, exp) +} + +//@ trusted +//@ preserves acc(l.Mem(), 1/16) +//@ preserves acc(Mem(exp), 1/16) +//@ ensures err == nil ==> Mem(res) +//@ ensures err == nil ==> Abs(res) == tm.expB(tm.generatorB(), Abs(exp)) +// arg is big-endian +func (l *LibraryState) DhExp(exp []byte) (res []byte, err error) { + g := big.NewInt(GroupGenerator) + return l.expModWithIntBase(g, exp) +} + +//@ trusted +//@ preserves acc(l.Mem(), 1/16) +//@ preserves acc(Mem(dhSecret), 1/16) && acc(Mem(dhHalfKey), 1/16) +//@ ensures err == nil ==> Mem(res) +//@ ensures err == nil ==> Abs(res) == tm.expB(Abs(dhHalfKey), Abs(dhSecret)) +// args are big-endian +func (l *LibraryState) DhSharedSecret(dhSecret, dhHalfKey []byte) (res []byte, err error) { + return l.expMod(dhHalfKey, dhSecret) +} diff --git a/ReusableVerificationLibrary/labeledlibrary/library/utils.go b/ReusableVerificationLibrary/labeledlibrary/library/utils.go new file mode 100644 index 0000000..28c42ae --- /dev/null +++ b/ReusableVerificationLibrary/labeledlibrary/library/utils.go @@ -0,0 +1,34 @@ +package library + +import ( + bytes "bytes" + "errors" + fmt "fmt" +) + + +//@ trusted +//@ decreases +//@ requires acc(Mem(s1), 1/16) +//@ requires acc(Mem(s2), 1/16) +//@ ensures acc(Mem(s1), 1/16) +//@ ensures acc(Mem(s2), 1/16) +// ensures res ==> Size(s1) == Size(s2) +//@ ensures res == (Abs(s1) == Abs(s2)) +// ensures res ==> unfolding acc(Mem(s1), 1/16) in unfolding acc(Mem(s2), 1/16) in forall i int :: { s1[i], s2[i] } 0 <= i && i < len(s1) ==> s1[i] == s2[i] +func Compare(s1, s2 ByteString) (res bool) { + return bytes.Compare(s1, s2) == 0 +} + +//@ trusted +//@ decreases +//@ ensures res != nil +func NewError(desc string) (res error) { + return errors.New("idB does not match") +} + +//@ trusted +//@ decreases +func Println(msg string) { + fmt.Println(msg) +} diff --git a/ReusableVerificationLibrary/labeledlibrary/security-properties.gobra b/ReusableVerificationLibrary/labeledlibrary/security-properties.gobra new file mode 100644 index 0000000..b465fe6 --- /dev/null +++ b/ReusableVerificationLibrary/labeledlibrary/security-properties.gobra @@ -0,0 +1,248 @@ +package labeledlibrary + +import ( + arb "github.com/viperproject/ReusableProtocolVerificationLibrary/arbitrary" + att "github.com/viperproject/ReusableProtocolVerificationLibrary/attacker" + ev "github.com/viperproject/ReusableProtocolVerificationLibrary/event" + "github.com/viperproject/ReusableProtocolVerificationLibrary/label" + "github.com/viperproject/ReusableProtocolVerificationLibrary/labeling" + tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" + tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" + p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" +) + + +// #region Security Properties +ghost +requires acc(l.Mem(), _) +// A term is secret if it is either unknown to the attacker or one of its readers (i.e. `honestIds`) +// has been corrupted. Currently, this property is restricted to the `Readers` label +// but could in principle be extended if necessary. +pure func (l *LabeledLibrary) Secrecy(term tm.Term, honestIds set[p.Id]) bool { + return (l.LabelCtx().IsLabeledRelaxed(l.Snapshot(), term, label.Readers(honestIds)) && + att.isUnknownToAttacker(l.Snapshot(), term)) || + tr.containsCorruptId(l.Snapshot().getCorruptIds(), honestIds) +} + +ghost +requires prev.isSuffix(snapshot) +// `honestIds` can be corrupted at any time and `prevHonestIds` must have been already been corrupted at `prev` +// in order that `term` is considered forward secret despite the attacker knowing the value. +pure func ForwardSecrecy(snapshot, prev tr.TraceEntry, term tm.Term, prevHonestIds, honestIds set[p.Id]) bool { + return att.isUnknownToAttacker(snapshot, term) || + tr.containsCorruptId(prev.getCorruptIds(), prevHonestIds) || + tr.containsCorruptId(snapshot.getCorruptIds(), honestIds) +} + +ghost +requires acc(l.Mem(), _) +pure func (l *LabeledLibrary) NonInjectiveAgreement(actor, peer p.Principal, commit, running ev.Event, honestIds set[p.Id]) bool { + return l.Ctx().EventConsistency(running) && + l.Ctx().EventConsistency(commit) && + l.Snapshot().eventOccurs(actor, commit) && + ( // there is another running session with the same parameters occurring before the commit event... + l.Snapshot().eventOccursWitness(actor, commit).eventOccurs(peer, running) || + // ... or corruption has occurred before the commit event: + tr.containsCorruptId(l.Snapshot().eventOccursWitness(actor, commit).getCorruptIds(), honestIds)) +} + +ghost +requires acc(l.Mem(), _) +pure func (l *LabeledLibrary) InjectiveAgreement(actor, peer p.Principal, commit, running ev.Event, honestIds set[p.Id]) bool { + return l.NonInjectiveAgreement(actor, peer, commit, running, honestIds) && + l.Ctx().IsUnique(commit.typ) && + (!tr.containsCorruptId(l.Snapshot().eventOccursWitness(actor, commit).getCorruptIds(), honestIds) ==> + // if no corruption has occurred, then there is no other commit with the same parameters: + l.EventIsUnique(actor, commit)) +} + +ghost +requires acc(l.Mem(), _) +pure func (l *LabeledLibrary) EventIsUnique(principal p.Principal, event ev.Event) bool { + return l.Ctx().EventConsistency(event) && + l.Ctx().IsUnique(event.typ) && + l.Snapshot().eventOccurs(principal, event) && + (forall principal2 p.Principal, event2 ev.Event :: { l.Snapshot().eventOccurs(principal2, event2) } !(event.typ == event2.typ && l.Ctx().EventConsistency(event2) && l.Ctx().UniquenessWitness(event) == l.Ctx().UniquenessWitness(event2) && l.Snapshot().eventOccurs(principal2, event2) && l.Snapshot().eventOccursAtTime(principal, event) != l.Snapshot().eventOccursAtTime(principal2, event2))) && + (forall prefix tr.TraceEntry :: { prefix.eventOccursWitness(principal, event) } prefix.isSuffix(l.Snapshot()) && prefix.eventOccurs(principal, event) ==> prefix.eventOccursWitness(principal, event) == l.Snapshot().eventOccursWitness(principal, event)) && + // this is the property as stated in the paper: + (forall principal2 p.Principal, event2 ev.Event, idx int :: { l.Snapshot().eventOccursAt(principal2, event2, idx) } (event.typ != event2.typ || !l.Ctx().EventConsistency(event2) || l.Ctx().UniquenessWitness(event) != l.Ctx().UniquenessWitness(event2) || idx == l.Snapshot().eventOccursAtTime(principal, event) - 1 || !l.Snapshot().eventOccursAt(principal2, event2, idx))) +} +// #endregion + + +// #region Lemmas related to the Security Properties +ghost +decreases +requires l.Mem() +requires len(readers) > 0 +requires l.LabelCtx().IsLabeledRelaxed(l.Snapshot(), term, label.Readers(readers)) || + tr.containsCorruptId(l.Snapshot().getCorruptIds(), readers) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +ensures l.Secrecy(term, readers) +// the following postcondition provides additional insights regarding the returned witness +ensures optCorruptedId == none[p.Id] ? + att.isUnknownToAttacker(l.Snapshot(), term) : + (get(optCorruptedId) in l.Snapshot().getCorruptIds() && + tr.containsId(readers, get(optCorruptedId))) +func (l *LabeledLibrary) SecrecyLemma(term tm.Term, readers set[p.Id]) (optCorruptedId option[p.Id]) { + if att.attackerKnows(l.Snapshot(), term) { + corruptedIds := l.Snapshot().getCorruptIds() + // we can call the optimized secrecy lemma only if no corruption has + // occurred as we get the correct labeling of `term` only in this case due to the disjunction in the + // precondition + if !tr.containsCorruptId(corruptedIds, readers) { + optCorruptedId = some(l.secrecyLemmaHelper(term, readers)) + } else { + assert exists corruptedId p.Id :: { tr.containsId(readers, corruptedId) } corruptedId in corruptedIds && tr.containsId(readers, corruptedId) + // get witness + corruptedId := arb.GetArbId() + assume corruptedId in corruptedIds && tr.containsId(readers, corruptedId) + optCorruptedId = some(corruptedId) + } + } else { + optCorruptedId = none[p.Id] + } +} + +ghost +decreases +requires l.Mem() +requires l.LabelCtx().IsLabeledRelaxed(l.Snapshot(), term, label.Readers(readers)) +requires att.attackerKnows(l.Snapshot(), term) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +ensures corruptedId in l.Snapshot().getCorruptIds() +ensures tr.containsId(readers, corruptedId) +func (l *LabeledLibrary) secrecyLemmaHelper(term tm.Term, readers set[p.Id]) (corruptedId p.Id) { + corruptedIds := (l.Snapshot()).getCorruptIds() + if tr.containsCorruptId(corruptedIds, readers) { + assert exists corruptedId p.Id :: { tr.containsId(readers, corruptedId) } corruptedId in corruptedIds && tr.containsId(readers, corruptedId) + // get witness + corruptedId = arb.GetArbId() + assume corruptedId in corruptedIds && tr.containsId(readers, corruptedId) + } else { + l.AttackerOnlyKnowsPublishableValues(term) + l.LabelCtx().PublishableRequiresCorruptionWeakened(l.Snapshot(), term, label.Readers(readers)) + // this branch is a contradiction: + // the preconditions state that `term` has a certain labeling and the + // branch condition states that no reader is corrupted. + // The lemma call's postcondition however expresses that `term` is publishable. + // This in turn implies that a reader must have been corrupted (based on `canFlowInternal`), + // which contradicts the branch condition. + assert false // contradiction -- as expected + } +} + + + +ghost +decreases +requires l.Mem() +requires (l.Snapshot()).eventOccurs(principal, event) +requires (l.Ctx()).IsUnique(event.typ) +requires (l.Ctx()).EventConsistency(event) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +ensures l.EventIsUnique(principal, event) +// all events with the same type and same uniqueness witness are guaranteed to occur only once on the trace +func (l *LabeledLibrary) UniqueEventIsUnique(principal p.Principal, event ev.Event) { + // we do a forall introduction, i.e. we show for an arbitrary arbPrincipal, arbEvent that the postcondition holds: + arbPrincipal := arb.GetArbPrincipal() + arbEvent := arb.GetArbEvent() + if event.typ == arbEvent.typ && + l.Ctx().EventConsistency(arbEvent) && + l.Ctx().UniquenessWitness(event) == l.Ctx().UniquenessWitness(arbEvent) && + l.Snapshot().eventOccurs(arbPrincipal, arbEvent) { + l.UniqueEventsAreUnique(principal, arbPrincipal, event, arbEvent) + } + assert !(event.typ == arbEvent.typ && l.Ctx().EventConsistency(arbEvent) && l.Ctx().UniquenessWitness(event) == l.Ctx().UniquenessWitness(arbEvent) && l.Snapshot().eventOccurs(arbPrincipal, arbEvent) && l.Snapshot().eventOccursAtTime(principal, event) != l.Snapshot().eventOccursAtTime(arbPrincipal, arbEvent)) + // due to forall introduction + assume forall principal2 p.Principal, event2 ev.Event :: { l.Snapshot().eventOccurs(principal2, event2) } !(event.typ == event2.typ && l.Ctx().EventConsistency(event2) && l.Ctx().UniquenessWitness(event) == l.Ctx().UniquenessWitness(event2) && l.Snapshot().eventOccurs(principal2, event2) && l.Snapshot().eventOccursAtTime(principal, event) != l.Snapshot().eventOccursAtTime(principal2, event2)) + + arbSuffix := arb.GetArbTraceEntry() + if arbSuffix.isSuffix(l.Snapshot()) && + arbSuffix.eventOccurs(principal, event) { + l.UniqueEventsOccurOnlyOnce(arbSuffix, principal, event) + } + assert arbSuffix.isSuffix(l.Snapshot()) && arbSuffix.eventOccurs(principal, event) ==> arbSuffix.eventOccursWitness(principal, event) == l.Snapshot().eventOccursWitness(principal, event) + // due to forall introduction + assume forall suffix tr.TraceEntry :: { suffix.eventOccursWitness(principal, event) } suffix.isSuffix(l.Snapshot()) && suffix.eventOccurs(principal, event) ==> suffix.eventOccursWitness(principal, event) == l.Snapshot().eventOccursWitness(principal, event) + + arbPrincipal = arb.GetArbPrincipal() + arbEvent = arb.GetArbEvent() + arbIndex := arb.GetArbInt() + if event.typ == arbEvent.typ && + l.Ctx().EventConsistency(arbEvent) && + l.Ctx().UniquenessWitness(event) == l.Ctx().UniquenessWitness(arbEvent) && + arbIndex != l.Snapshot().eventOccursAtTime(principal, event) - 1 && + l.Snapshot().eventOccursAt(arbPrincipal, arbEvent, arbIndex) { + idx1 := l.Snapshot().eventOccursAtTimeLemma(principal, event) + l.UniqueEventsAreUniqueAt(principal, arbPrincipal, event, arbEvent, idx1, arbIndex) + } + assert event.typ != arbEvent.typ || !l.Ctx().EventConsistency(arbEvent) || l.Ctx().UniquenessWitness(event) != l.Ctx().UniquenessWitness(arbEvent) || arbIndex == l.Snapshot().eventOccursAtTime(principal, event) - 1 || !l.Snapshot().eventOccursAt(arbPrincipal, arbEvent, arbIndex) + // due to forall introduction + assume forall principal2 p.Principal, event2 ev.Event, idx int :: { l.Snapshot().eventOccursAt(principal2, event2, idx) } event.typ != event2.typ || !l.Ctx().EventConsistency(event2) || l.Ctx().UniquenessWitness(event) != l.Ctx().UniquenessWitness(event2) || idx == l.Snapshot().eventOccursAtTime(principal, event) - 1 || !l.Snapshot().eventOccursAt(principal2, event2, idx) +} + +ghost +decreases +requires l.Mem() +requires l.Snapshot().eventOccurs(principal1, event1) +requires l.Snapshot().eventOccurs(principal2, event2) +requires event1.typ == event2.typ +requires l.Ctx().IsUnique(event1.typ) +requires l.Ctx().EventConsistency(event1) && l.Ctx().EventConsistency(event2) +requires l.Ctx().UniquenessWitness(event1) == l.Ctx().UniquenessWitness(event2) +ensures l.Mem() +ensures principal1 == principal2 +ensures event1 == event2 +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +func (l *LabeledLibrary) UniqueEventsAreUnique(principal1, principal2 p.Principal, event1, event2 ev.Event) { + idx1 := l.Snapshot().eventOccursAtTimeLemma(principal1, event1) + idx2 := l.Snapshot().eventOccursAtTimeLemma(principal2, event2) + l.UniqueEventsAreUniqueAt(principal1, principal2, event1, event2, idx1, idx2) +} + +ghost +decreases +requires l.Mem() +requires l.Snapshot().eventOccursAt(principal1, event1, idx1) +requires l.Snapshot().eventOccursAt(principal2, event2, idx2) +requires event1.typ == event2.typ +requires l.Ctx().IsUnique(event1.typ) +requires l.Ctx().EventConsistency(event1) && l.Ctx().EventConsistency(event2) +requires l.Ctx().UniquenessWitness(event1) == l.Ctx().UniquenessWitness(event2) +ensures l.Mem() +ensures principal1 == principal2 +ensures event1 == event2 +ensures idx1 == idx2 +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +func (l *LabeledLibrary) UniqueEventsAreUniqueAt(principal1, principal2 p.Principal, event1, event2 ev.Event, idx1, idx2 int) { + unfold l.Mem() + l.manager.UniqueEventsAreUniqueAt(l.ctx, l.owner, principal1, principal2, event1, event2, idx1, idx2) + fold l.Mem() +} + +ghost +decreases +requires l.Mem() +requires suffix.isSuffix(l.Snapshot()) +requires suffix.eventOccurs(principal, event) +requires l.Ctx().IsUnique(event.typ) +ensures l.Mem() +ensures l.Snapshot().eventOccurs(principal, event) +ensures suffix.eventOccursWitness(principal, event) == l.Snapshot().eventOccursWitness(principal, event) +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +func (l *LabeledLibrary) UniqueEventsOccurOnlyOnce(suffix tr.TraceEntry, principal p.Principal, event ev.Event) { + unfold l.Mem() + l.manager.UniqueEventsOccurOnlyOnce(l.ctx, l.owner, suffix, principal, event) + fold l.Mem() +} +// #endregion diff --git a/ReusableVerificationLibrary/labeledlibrary/state.go b/ReusableVerificationLibrary/labeledlibrary/state.go new file mode 100644 index 0000000..0435844 --- /dev/null +++ b/ReusableVerificationLibrary/labeledlibrary/state.go @@ -0,0 +1,454 @@ +package labeledlibrary + +import ( + //@ arb "github.com/viperproject/ReusableProtocolVerificationLibrary/arbitrary" + //@ att "github.com/viperproject/ReusableProtocolVerificationLibrary/attacker" + //@ ev "github.com/viperproject/ReusableProtocolVerificationLibrary/event" + //@ "github.com/viperproject/ReusableProtocolVerificationLibrary/label" + //@ "github.com/viperproject/ReusableProtocolVerificationLibrary/labeling" + lib "github.com/viperproject/ReusableProtocolVerificationLibrary/labeledlibrary/library" + //@ p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" + //@ tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" + //@ tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" + //@ tri "github.com/viperproject/ReusableProtocolVerificationLibrary/traceinvariant" + //@ tman "github.com/viperproject/ReusableProtocolVerificationLibrary/tracemanager" + //@ ts "github.com/viperproject/ReusableProtocolVerificationLibrary/concurrentdatastructure" + //@ u "github.com/viperproject/ReusableProtocolVerificationLibrary/usage" +) + +// TODO ghost fields should be ghost +type LabeledLibrary struct { + s *lib.LibraryState + com Communication + //@ ctx tri.TraceContext + //@ manager *tman.TraceManager + //@ owner p.Id +} + +/*@ +pred (l *LabeledLibrary) Mem() { + acc(l) && + acc(l.s.Mem(), 1/8) && + acc(l.com.LibMem(), 1/8) && + l.com != nil && isComparable(l.com) && + l.ctx != nil && isComparable(l.ctx) && l.ctx.Props() && + l.manager.Mem(l.ctx, l.owner) +} + +// abstract over all memory that remains unchanged after library initialization +// TODO should be ghost +type ImmutableState struct { + l LabeledLibrary // the entire struct remains constant after initialization + managerState tman.ImmutableState +} + +ghost +requires acc(l.Mem(), _) +pure func (l *LabeledLibrary) ImmutableState() ImmutableState { + return unfolding acc(l.Mem(), _) in ImmutableState{ *l, l.manager.ImmutableState(l.ctx, l.owner) } +} + +ghost +requires acc(l.Mem(), _) +ensures res != nil && isComparable(res) && res.Props() +pure func (l *LabeledLibrary) Ctx() (res tri.TraceContext) { + return unfolding acc(l.Mem(), _) in l.ctx +} + +ghost +requires acc(l.Mem(), _) +pure func (l *LabeledLibrary) Manager() *tman.TraceManager { + return unfolding acc(l.Mem(), _) in l.manager +} + +ghost +requires acc(l.Mem(), _) +pure func (l *LabeledLibrary) Owner() p.Id { + return unfolding acc(l.Mem(), _) in l.owner +} + +ghost +requires acc(l.Mem(), _) +pure func (l *LabeledLibrary) OwnerWoThread() p.Id { + return unfolding acc(l.Mem(), _) in l.owner.IsSessionThread() ? p.sessionId(p.getIdPrincipal(l.owner), p.getIdSession(l.owner)) : l.owner +} + +ghost +requires acc(l.Mem(), _) +pure func (l *LabeledLibrary) LabelCtx() labeling.LabelingContext { + return tri.GetLabeling(l.Ctx()) +} + +ghost +requires acc(l.Mem(), _) +pure func (l *LabeledLibrary) Snapshot() tr.TraceEntry { + return unfolding acc(l.Mem(), _) in l.manager.Snapshot(l.ctx, l.owner) +} +@*/ + +//@ requires acc(s.Mem(), 1/8) +//@ requires acc(com.LibMem(), 1/8) +//@ requires com != nil && isComparable(com) +//@ requires manager.Mem(ctx, owner) +//@ requires ctx != nil && isComparable(ctx) && ctx.Props() +//@ ensures res.Mem() +//@ ensures res.Ctx() == ctx +//@ ensures res.Manager() == manager +//@ ensures res.Owner() == owner +//@ ensures (res.ImmutableState()).managerState == old(manager.ImmutableState(ctx, owner)) +//@ ensures res.Snapshot() == old(manager.Snapshot(ctx, owner)) +// TODO manager, ctx, owner should be ghost +func NewLabeledLibrary(s *lib.LibraryState, com Communication /*@, manager *tman.TraceManager, ctx tri.TraceContext, owner p.Id @*/) (res *LabeledLibrary) { + res = &LabeledLibrary{ s, com /*@, ctx, manager, owner @*/ } + //@ fold res.Mem() + return +} + +/*@ +ghost +decreases +requires l.Mem() +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +ensures forall oldSnap tr.TraceEntry :: { oldSnap.getCorruptIds() } oldSnap.isSuffix(l.Snapshot()) ==> oldSnap.getCorruptIds() subset (l.Snapshot()).getCorruptIds() +ensures forall oldSnap tr.TraceEntry, term tm.Term :: { l.LabelCtx().IsValid(oldSnap, term) } oldSnap.isSuffix(l.Snapshot()) && l.LabelCtx().IsValid(oldSnap, term) ==> l.LabelCtx().IsValid(l.Snapshot(), term) +ensures forall oldSnap tr.TraceEntry, term tm.Term, sLabel label.SecrecyLabel :: { l.LabelCtx().IsLabeled(oldSnap, term, sLabel) } oldSnap.isSuffix(l.Snapshot()) && l.LabelCtx().IsLabeled(oldSnap, term, sLabel) ==> l.LabelCtx().IsLabeled(l.Snapshot(), term, sLabel) +ensures forall oldSnap tr.TraceEntry, l1, l2 label.SecrecyLabel :: { l.LabelCtx().CanFlow(oldSnap, l1, l2) } oldSnap.isSuffix(l.Snapshot()) && l.LabelCtx().CanFlow(oldSnap, l1, l2) ==> l.LabelCtx().CanFlow(l.Snapshot(), l1, l2) +ensures forall oldSnap tr.TraceEntry, term tm.Term, sLabel label.SecrecyLabel, usage u.Usage :: { l.LabelCtx().IsSecret(oldSnap, term, sLabel, usage) } oldSnap.isSuffix(l.Snapshot()) && l.LabelCtx().IsSecret(oldSnap, term, sLabel, usage) ==> l.LabelCtx().IsSecret(l.Snapshot(), term, sLabel, usage) +ensures forall oldSnap tr.TraceEntry, term tm.Term, sLabel label.SecrecyLabel :: { l.LabelCtx().IsMsg(oldSnap, term, sLabel) } oldSnap.isSuffix(l.Snapshot()) && l.LabelCtx().IsMsg(oldSnap, term, sLabel) ==> l.LabelCtx().IsMsg(l.Snapshot(), term, sLabel) +ensures forall oldSnap tr.TraceEntry, owner p.Id, sk tm.Term, keyType labeling.KeyType, usage string :: { l.LabelCtx().IsSecretKey(oldSnap, owner, sk, keyType, usage) } oldSnap.isSuffix(l.Snapshot()) && l.LabelCtx().IsSecretKey(oldSnap, owner, sk, keyType, usage) ==> l.LabelCtx().IsSecretKey(l.Snapshot(), owner, sk, keyType, usage) +ensures forall oldSnap tr.TraceEntry, owner p.Id, pk, sk tm.Term, keyType labeling.KeyType, usage string :: { l.LabelCtx().IsPublicKey(oldSnap, owner, pk, sk, keyType, usage) } oldSnap.isSuffix(l.Snapshot()) && l.LabelCtx().IsPublicKey(oldSnap, owner, pk, sk, keyType, usage) ==> l.LabelCtx().IsPublicKey(l.Snapshot(), owner, pk, sk, keyType, usage) +ensures forall oldSnap tr.TraceEntry, owner p.Id, pk tm.Term, keyType labeling.KeyType, usage string :: { l.LabelCtx().IsPublicKeyExistential(oldSnap, owner, pk, keyType, usage) } oldSnap.isSuffix(l.Snapshot()) && l.LabelCtx().IsPublicKeyExistential(oldSnap, owner, pk, keyType, usage) ==> l.LabelCtx().IsPublicKeyExistential(l.Snapshot(), owner, pk, keyType, usage) +ensures forall oldSnap tr.TraceEntry, principal p.Principal, event ev.Event :: { oldSnap.eventOccurs(principal, event) } oldSnap.isSuffix(l.Snapshot()) && oldSnap.eventOccurs(principal, event) ==> l.Snapshot().eventOccurs(principal, event) +ensures forall oldSnap tr.TraceEntry, principal p.Principal, event ev.Event :: { oldSnap.eventOccursWitness(principal, event) } oldSnap.isSuffix(l.Snapshot()) && oldSnap.eventOccurs(principal, event) ==> oldSnap.eventOccursWitness(principal, event).isSuffix(l.Snapshot().eventOccursWitness(principal, event)) +ensures forall oldSnap tr.TraceEntry, sender, receiver p.Principal, payload tm.Term :: { oldSnap.messageOccurs(sender, receiver, payload) } oldSnap.isSuffix(l.Snapshot()) &&oldSnap.messageOccurs(sender, receiver, payload) ==> (l.Snapshot()).messageOccurs(sender, receiver, payload) +ensures forall oldSnap tr.TraceEntry, nonce tm.Term :: { oldSnap.OnlyNonceOccurs(nonce) } oldSnap.isSuffix(l.Snapshot()) && oldSnap.OnlyNonceOccurs(nonce) ==> (l.Snapshot()).OnlyNonceOccurs(nonce) +ensures forall oldSnap tr.TraceEntry, nonce tm.Term, nonceLabel label.SecrecyLabel, nonceUsage u.Usage :: { oldSnap.nonceOccurs(nonce, nonceLabel, nonceUsage) } oldSnap.isSuffix(l.Snapshot()) && oldSnap.nonceOccurs(nonce, nonceLabel, nonceUsage) ==> (l.Snapshot()).nonceOccurs(nonce, nonceLabel, nonceUsage) +func (l *LabeledLibrary) ApplyMonotonicity() { + l.ApplyMonotonicityWithSnap(l.Snapshot()) +} + +ghost +decreases +requires l.Mem() +requires snap.isSuffix(l.Snapshot()) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +ensures forall oldSnap tr.TraceEntry :: { oldSnap.getCorruptIds() } oldSnap.isSuffix(snap) ==> oldSnap.getCorruptIds() subset (snap).getCorruptIds() +ensures forall oldSnap tr.TraceEntry, term tm.Term :: { l.LabelCtx().IsValid(oldSnap, term) } oldSnap.isSuffix(snap) && l.LabelCtx().IsValid(oldSnap, term) ==> l.LabelCtx().IsValid(snap, term) +ensures forall oldSnap tr.TraceEntry, term tm.Term, sLabel label.SecrecyLabel :: { l.LabelCtx().IsLabeled(oldSnap, term, sLabel) } oldSnap.isSuffix(snap) && l.LabelCtx().IsLabeled(oldSnap, term, sLabel) ==> l.LabelCtx().IsLabeled(snap, term, sLabel) +ensures forall oldSnap tr.TraceEntry, l1, l2 label.SecrecyLabel :: { l.LabelCtx().CanFlow(oldSnap, l1, l2) } oldSnap.isSuffix(snap) && l.LabelCtx().CanFlow(oldSnap, l1, l2) ==> l.LabelCtx().CanFlow(snap, l1, l2) +ensures forall oldSnap tr.TraceEntry, term tm.Term, sLabel label.SecrecyLabel, usage u.Usage :: { l.LabelCtx().IsSecret(oldSnap, term, sLabel, usage) } oldSnap.isSuffix(snap) && l.LabelCtx().IsSecret(oldSnap, term, sLabel, usage) ==> l.LabelCtx().IsSecret(snap, term, sLabel, usage) +ensures forall oldSnap tr.TraceEntry, term tm.Term, sLabel label.SecrecyLabel :: { l.LabelCtx().IsMsg(oldSnap, term, sLabel) } oldSnap.isSuffix(snap) && l.LabelCtx().IsMsg(oldSnap, term, sLabel) ==> l.LabelCtx().IsMsg(snap, term, sLabel) +ensures forall oldSnap tr.TraceEntry, owner p.Id, sk tm.Term, keyType labeling.KeyType, usage string :: { l.LabelCtx().IsSecretKey(oldSnap, owner, sk, keyType, usage) } oldSnap.isSuffix(snap) && l.LabelCtx().IsSecretKey(oldSnap, owner, sk, keyType, usage) ==> l.LabelCtx().IsSecretKey(snap, owner, sk, keyType, usage) +ensures forall oldSnap tr.TraceEntry, owner p.Id, pk, sk tm.Term, keyType labeling.KeyType, usage string :: { l.LabelCtx().IsPublicKey(oldSnap, owner, pk, sk, keyType, usage) } oldSnap.isSuffix(snap) && l.LabelCtx().IsPublicKey(oldSnap, owner, pk, sk, keyType, usage) ==> l.LabelCtx().IsPublicKey(snap, owner, pk, sk, keyType, usage) +ensures forall oldSnap tr.TraceEntry, owner p.Id, pk tm.Term, keyType labeling.KeyType, usage string :: { l.LabelCtx().IsPublicKeyExistential(oldSnap, owner, pk, keyType, usage) } oldSnap.isSuffix(snap) && l.LabelCtx().IsPublicKeyExistential(oldSnap, owner, pk, keyType, usage) ==> l.LabelCtx().IsPublicKeyExistential(snap, owner, pk, keyType, usage) +ensures forall oldSnap tr.TraceEntry, principal p.Principal, event ev.Event :: { oldSnap.eventOccurs(principal, event) } oldSnap.isSuffix(snap) && oldSnap.eventOccurs(principal, event) ==> snap.eventOccurs(principal, event) +ensures forall oldSnap tr.TraceEntry, principal p.Principal, event ev.Event :: { oldSnap.eventOccursWitness(principal, event) } oldSnap.isSuffix(snap) && oldSnap.eventOccurs(principal, event) ==> oldSnap.eventOccursWitness(principal, event).isSuffix(snap.eventOccursWitness(principal, event)) +ensures forall oldSnap tr.TraceEntry, sender, receiver p.Principal, payload tm.Term :: { oldSnap.messageOccurs(sender, receiver, payload) } oldSnap.isSuffix(snap) &&oldSnap.messageOccurs(sender, receiver, payload) ==> (snap).messageOccurs(sender, receiver, payload) +ensures forall oldSnap tr.TraceEntry, nonce tm.Term :: { oldSnap.OnlyNonceOccurs(nonce) } oldSnap.isSuffix(snap) && oldSnap.OnlyNonceOccurs(nonce) ==> (snap).OnlyNonceOccurs(nonce) +ensures forall oldSnap tr.TraceEntry, nonce tm.Term, nonceLabel label.SecrecyLabel, nonceUsage u.Usage :: { oldSnap.nonceOccurs(nonce, nonceLabel, nonceUsage) } oldSnap.isSuffix(snap) && oldSnap.nonceOccurs(nonce, nonceLabel, nonceUsage) ==> (snap).nonceOccurs(nonce, nonceLabel, nonceUsage) +func (l *LabeledLibrary) ApplyMonotonicityWithSnap(snap tr.TraceEntry) { + labelCtx := l.LabelCtx() + // forall introduction + arbSnap := arb.GetArbTraceEntry() + arbTerm := arb.GetArbTerm() + arbLabel := arb.GetArbLabel() + arbLabel2 := arb.GetArbLabel() + arbPrincipal := arb.GetArbPrincipal() + arbEvent := arb.GetArbEvent() + arbSender := arb.GetArbPrincipal() + arbReceiver := arb.GetArbPrincipal() + arbPayload := arb.GetArbTerm() + arbNonceLabel := arb.GetArbLabel() + arbNonceUsage := arb.GetArbUsage() + if (arbSnap.isSuffix(snap)) { + arbSnap.getCorruptIdsMonotonic(snap) + if (labelCtx.IsValid(arbSnap, arbTerm)) { + labelCtx.IsValidMonotonic(arbSnap, snap, arbTerm) + } + if (labelCtx.CanFlow(arbSnap, arbLabel, arbLabel2)) { + labelCtx.CanFlowMonotonic(arbSnap, snap, arbLabel, arbLabel2) + } + if (labelCtx.IsMsg(arbSnap, arbTerm, arbLabel)) { + labelCtx.IsMsgMonotonic(arbSnap, snap, arbTerm, arbLabel) + } + if (arbSnap.eventOccurs(arbPrincipal, arbEvent)) { + arbSnap.eventOccursMonotonic(snap, arbPrincipal, arbEvent) + arbSnap.eventOccursWitnessMonotonic(snap, arbPrincipal, arbEvent) + } + if (arbSnap.messageOccurs(arbSender, arbReceiver, arbPayload)) { + arbSnap.messageOccursMonotonic(snap, arbSender, arbReceiver, arbPayload) + } + if (arbSnap.OnlyNonceOccurs(arbTerm)) { + arbSnap.OnlyNonceOccursMonotonic(snap, arbTerm) + } + if (arbSnap.nonceOccurs(arbTerm, arbNonceLabel, arbNonceUsage)) { + arbSnap.nonceOccursMonotonic(snap, arbTerm, arbNonceLabel, arbNonceUsage) + } + } + assert arbSnap.isSuffix(snap) ==> arbSnap.getCorruptIds() subset (snap).getCorruptIds() + assume forall oldSnap tr.TraceEntry :: { oldSnap.getCorruptIds() } oldSnap.isSuffix(snap) ==> oldSnap.getCorruptIds() subset (snap).getCorruptIds() + assert arbSnap.isSuffix(snap) && labelCtx.IsValid(arbSnap, arbTerm) ==> labelCtx.IsValid(snap, arbTerm) + assume forall oldSnap tr.TraceEntry, term tm.Term :: { labelCtx.IsValid(oldSnap, term) } oldSnap.isSuffix(snap) && labelCtx.IsValid(oldSnap, term) ==> labelCtx.IsValid(snap, term) + assert arbSnap.isSuffix(snap) && labelCtx.CanFlow(arbSnap, arbLabel, arbLabel2) ==> labelCtx.CanFlow(snap, arbLabel, arbLabel2) + assume forall oldSnap tr.TraceEntry, l1, l2 label.SecrecyLabel :: { labelCtx.CanFlow(oldSnap, l1, l2) } oldSnap.isSuffix(snap) && labelCtx.CanFlow(oldSnap, l1, l2) ==> labelCtx.CanFlow(snap, l1, l2) + assert arbSnap.isSuffix(snap) && labelCtx.IsMsg(arbSnap, arbTerm, arbLabel) ==> labelCtx.IsMsg(snap, arbTerm, arbLabel) + assume forall oldSnap tr.TraceEntry, term tm.Term, sLabel label.SecrecyLabel :: { labelCtx.IsMsg(oldSnap, term, sLabel) } oldSnap.isSuffix(snap) && labelCtx.IsMsg(oldSnap, term, sLabel) ==> labelCtx.IsMsg(snap, term, sLabel) + assert arbSnap.isSuffix(snap) && arbSnap.eventOccurs(arbPrincipal, arbEvent) ==> snap.eventOccurs(arbPrincipal, arbEvent) + assume forall oldSnap tr.TraceEntry, principal p.Principal, event ev.Event :: { oldSnap.eventOccurs(principal, event) } oldSnap.isSuffix(snap) && oldSnap.eventOccurs(principal, event) ==> (snap).eventOccurs(principal, event) + assert arbSnap.isSuffix(snap) && arbSnap.eventOccurs(arbPrincipal, arbEvent) ==> arbSnap.eventOccursWitness(arbPrincipal, arbEvent).isSuffix(snap.eventOccursWitness(arbPrincipal, arbEvent)) + assume forall oldSnap tr.TraceEntry, principal p.Principal, event ev.Event :: { oldSnap.eventOccursWitness(principal, event) } oldSnap.isSuffix(snap) && oldSnap.eventOccurs(principal, event) ==> oldSnap.eventOccursWitness(principal, event).isSuffix(snap.eventOccursWitness(principal, event)) + assert arbSnap.isSuffix(snap) && arbSnap.messageOccurs(arbSender, arbReceiver, arbPayload) ==> (snap).messageOccurs(arbSender, arbReceiver, arbPayload) + assume forall oldSnap tr.TraceEntry, sender, receiver p.Principal, payload tm.Term :: { oldSnap.messageOccurs(sender, receiver, payload) } oldSnap.isSuffix(snap) && oldSnap.messageOccurs(sender, receiver, payload) ==> (snap).messageOccurs(sender, receiver, payload) + assert arbSnap.isSuffix(snap) && arbSnap.OnlyNonceOccurs(arbTerm) ==> (snap).OnlyNonceOccurs(arbTerm) + assume forall oldSnap tr.TraceEntry, nonce tm.Term :: { oldSnap.OnlyNonceOccurs(nonce) } oldSnap.isSuffix(snap) && oldSnap.OnlyNonceOccurs(nonce) ==> (snap).OnlyNonceOccurs(nonce) + assert arbSnap.isSuffix(snap) && arbSnap.nonceOccurs(arbTerm, arbNonceLabel, arbNonceUsage) ==> (snap).nonceOccurs(arbTerm, arbNonceLabel, arbNonceUsage) + assume forall oldSnap tr.TraceEntry, nonce tm.Term, nonceLabel label.SecrecyLabel, nonceUsage u.Usage :: { oldSnap.nonceOccurs(nonce, nonceLabel, nonceUsage) } oldSnap.isSuffix(snap) && oldSnap.nonceOccurs(nonce, nonceLabel, nonceUsage) ==> (snap).nonceOccurs(nonce, nonceLabel, nonceUsage) + + // IsPublicKey does not require any proof steps but IsPublicKeyExistential does: + arbOwner := arb.GetArbId() + arbPk := arb.GetArbTerm() + arbKeyType := labeling.GetArbKeyType() + arbUsageString := arb.GetArbString() + if (arbSnap.isSuffix(snap) && + labelCtx.IsPublicKeyExistential(arbSnap, arbOwner, arbPk, arbKeyType, arbUsageString)) { + skWitness := arb.GetArbTerm() + if arbKeyType == labeling.KeyTypePke() { + assert exists sk tm.Term :: { labelCtx.IsPublicEncKey(arbSnap, arbOwner, arbPk, sk, arbUsageString) } labelCtx.IsPublicEncKey(arbSnap, arbOwner, arbPk, sk, arbUsageString) + // get witness + assume labelCtx.IsPublicEncKey(arbSnap, arbOwner, arbPk, skWitness, arbUsageString) + } else if arbKeyType == labeling.KeyTypeDh() { + assert exists sk tm.Term :: { labelCtx.IsPublicDhKey(arbSnap, arbOwner, arbPk, sk, arbUsageString) } labelCtx.IsPublicDhKey(arbSnap, arbOwner, arbPk, sk, arbUsageString) + // get witness + assume labelCtx.IsPublicDhKey(arbSnap, arbOwner, arbPk, skWitness, arbUsageString) + } else if arbKeyType == labeling.KeyTypeSigning() { + assert exists sk tm.Term :: { labelCtx.IsPublicSigningKey(arbSnap, arbOwner, arbPk, sk, arbUsageString) } labelCtx.IsPublicSigningKey(arbSnap, arbOwner, arbPk, sk, arbUsageString) + // get witness + assume labelCtx.IsPublicSigningKey(arbSnap, arbOwner, arbPk, skWitness, arbUsageString) + } + assert labelCtx.IsPublicKey(arbSnap, arbOwner, arbPk, skWitness, arbKeyType, arbUsageString) + assert labelCtx.IsPublicKey(snap, arbOwner, arbPk, skWitness, arbKeyType, arbUsageString) + assert labelCtx.IsPublicKeyExistential(snap, arbOwner, arbPk, arbKeyType, arbUsageString) + } + assert arbSnap.isSuffix(snap) && labelCtx.IsPublicKeyExistential(arbSnap, arbOwner, arbPk, arbKeyType, arbUsageString) ==> labelCtx.IsPublicKeyExistential(snap, arbOwner, arbPk, arbKeyType, arbUsageString) + assume forall oldSnap tr.TraceEntry, owner p.Id, pk tm.Term, keyType labeling.KeyType, usage string :: { labelCtx.IsPublicKeyExistential(oldSnap, owner, pk, keyType, usage) } oldSnap.isSuffix(snap) && labelCtx.IsPublicKeyExistential(oldSnap, owner, pk, keyType, usage) ==> labelCtx.IsPublicKeyExistential(snap, owner, pk, keyType, usage) +} + +ghost +decreases +requires l.Mem() +requires att.attackerKnows(l.Snapshot(), term) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +ensures l.LabelCtx().IsPublishable(l.Snapshot(), term) +func (l *LabeledLibrary) AttackerOnlyKnowsPublishableValues(term tm.Term) { + l.AttackerOnlyKnowsPublishableValuesWithSnap(l.Snapshot(), term) +} + +ghost +decreases +requires l.Mem() +requires snap.isSuffix(l.Snapshot()) +requires att.attackerKnows(snap, term) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +ensures l.LabelCtx().IsPublishable(snap, term) +func (l *LabeledLibrary) AttackerOnlyKnowsPublishableValuesWithSnap(snap tr.TraceEntry, term tm.Term) { + publicTerms := snap.getPublicTerms() + msgPayloads := snap.getMessagePayloads() + publishedTerms := snap.getTermsMadePublic() + + if term in publicTerms { + prev := l.PublicTermImpliesPublicInvWithSnap(snap, term) + l.LabelCtx().IsPublishableMonotonic(prev, snap, term) + } else if term in msgPayloads { + sender, receiver := snap.getMsgSenderReceiver(term) + prev := l.MessageOccursImpliesMessageInvWithSnap(snap, sender, receiver, term) + tr.getPrev(prev).isSuffixTransitive(prev, snap) + l.LabelCtx().IsPublishableMonotonic(tr.getPrev(prev), snap, term) + } else { + // assert term in publishedTerms + prev := l.PublishedTermImpliesMadePublicInvWithSnap(snap, term) + tr.getPrev(prev).isSuffixTransitive(prev, snap) + l.LabelCtx().IsPublishableMonotonic(tr.getPrev(prev), snap, term) + } +} + +ghost +decreases +requires l.Mem() +requires l.Snapshot().eventOccurs(principal, event) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +ensures prev.isSuffix(l.Snapshot()) +ensures prev.isEventAt(principal, event) +ensures prev == l.Snapshot().eventOccursWitness(principal, event) +ensures l.Ctx().pureEventInv(principal, event, tr.getPrev(prev)) +ensures l.Ctx().pureEventInv(principal, event, l.Snapshot()) +func (l *LabeledLibrary) EventOccursImpliesEventInv(principal p.Principal, event ev.Event) (prev tr.TraceEntry) { + prev = l.EventOccursImpliesEventInvWithSnap(l.Snapshot(), principal, event) +} + +ghost +decreases +requires l.Mem() +requires snap.isSuffix(l.Snapshot()) +requires snap.eventOccurs(principal, event) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +ensures prev.isSuffix(snap) +ensures prev.isEventAt(principal, event) +ensures prev == snap.eventOccursWitness(principal, event) +ensures l.Ctx().pureEventInv(principal, event, tr.getPrev(prev)) +ensures l.Ctx().pureEventInv(principal, event, snap) +ensures l.Ctx().pureEventInv(principal, event, l.Snapshot()) +func (l *LabeledLibrary) EventOccursImpliesEventInvWithSnap(snap tr.TraceEntry, principal p.Principal, event ev.Event) (prev tr.TraceEntry) { + unfold l.Mem() + prev = l.manager.EventOccursImpliesEventInvWithSnap(l.ctx, l.owner, snap, principal, event) + fold l.Mem() + tr.getPrev(prev).isSuffixTransitive(prev, snap) + l.Ctx().pureEventInvMonotonic(principal, event, tr.getPrev(prev), snap) + l.Ctx().pureEventInvMonotonic(principal, event, snap, l.Snapshot()) +} + +ghost +decreases +requires l.Mem() +requires (l.Snapshot()).OnlyNonceOccurs(nonce) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +ensures prev.isSuffix(l.Snapshot()) +ensures prev.isNonceAt(nonce) +ensures tri.pureRandInv(l.Ctx(), nonce, tr.getPrev(prev)) +ensures tri.pureRandInv(l.Ctx(), nonce, l.Snapshot()) +func (l *LabeledLibrary) NonceOccursImpliesRandInv(nonce tm.Term) (prev tr.TraceEntry) { + unfold l.Mem() + prev = l.manager.NonceOccursImpliesRandInv(l.ctx, l.owner, nonce) + fold l.Mem() + tr.getPrev(prev).isSuffixTransitive(prev, l.Snapshot()) + tri.pureRandInvTransitive(l.Ctx(), nonce, tr.getPrev(prev), l.Snapshot()) +} + +ghost +decreases +requires l.Mem() +requires term in l.Snapshot().getPublicTerms() +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +ensures prev.isSuffix(l.Snapshot()) +ensures prev.isRoot() +ensures tri.publicInv(l.Ctx(), l.Snapshot().getPublicTerms(), prev) +func (l *LabeledLibrary) PublicTermImpliesPublicInv(term tm.Term) (prev tr.TraceEntry) { + prev = l.PublicTermImpliesPublicInvWithSnap(l.Snapshot(), term) +} + +ghost +decreases +requires l.Mem() +requires snap.isSuffix(l.Snapshot()) +requires term in snap.getPublicTerms() +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +ensures prev.isSuffix(snap) +ensures prev.isSuffix(l.Snapshot()) +ensures prev.isRoot() +ensures tri.publicInv(l.Ctx(), snap.getPublicTerms(), prev) +func (l *LabeledLibrary) PublicTermImpliesPublicInvWithSnap(snap tr.TraceEntry, term tm.Term) (prev tr.TraceEntry) { + unfold l.Mem() + prev = l.manager.PublicTermImpliesPublicInvWithSnap(l.ctx, l.owner, snap, term) + fold l.Mem() +} + +ghost +decreases +requires l.Mem() +requires l.Snapshot().messageOccurs(sender, receiver, msg) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +ensures prev.isSuffix(l.Snapshot()) +ensures prev.isMessageAt(sender, receiver, msg) +ensures tri.messageInv(l.Ctx(), sender, receiver, msg, tr.getPrev(prev)) +ensures tri.messageInv(l.Ctx(), sender, receiver, msg, l.Snapshot()) +func (l *LabeledLibrary) MessageOccursImpliesMessageInv(sender, receiver p.Principal, msg tm.Term) (prev tr.TraceEntry) { + prev = l.MessageOccursImpliesMessageInvWithSnap(l.Snapshot(), sender, receiver, msg) +} + +ghost +decreases +requires l.Mem() +requires snap.isSuffix(l.Snapshot()) +requires snap.messageOccurs(sender, receiver, msg) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +ensures prev.isSuffix(snap) +ensures prev.isSuffix(l.Snapshot()) +ensures prev.isMessageAt(sender, receiver, msg) +ensures tri.messageInv(l.Ctx(), sender, receiver, msg, tr.getPrev(prev)) +ensures tri.messageInv(l.Ctx(), sender, receiver, msg, snap) +func (l *LabeledLibrary) MessageOccursImpliesMessageInvWithSnap(snap tr.TraceEntry, sender, receiver p.Principal, msg tm.Term) (prev tr.TraceEntry) { + unfold l.Mem() + prev = l.manager.MessageOccursImpliesMessageInvWithSnap(l.ctx, l.owner, snap, sender, receiver, msg) + fold l.Mem() + tr.getPrev(prev).isSuffixTransitive(prev, snap) + tr.getPrev(prev).isSuffixTransitive(prev, l.Snapshot()) + tri.messageInvTransitive(l.Ctx(), sender, receiver, msg, tr.getPrev(prev), snap) +} + +ghost +decreases +requires l.Mem() +requires term in l.Snapshot().getTermsMadePublic() +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +ensures prev.isSuffix(l.Snapshot()) +ensures prev.isPublic() +ensures tri.madePublicInv(l.Ctx(), term, tr.getPrev(prev)) +func (l *LabeledLibrary) PublishedTermImpliesMadePublicInv(term tm.Term) (prev tr.TraceEntry) { + prev = l.PublishedTermImpliesMadePublicInvWithSnap(l.Snapshot(), term) +} + +ghost +decreases +requires l.Mem() +requires snap.isSuffix(l.Snapshot()) +requires term in snap.getTermsMadePublic() +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures l.Snapshot() == old(l.Snapshot()) +ensures prev.isSuffix(snap) +ensures prev.isSuffix(l.Snapshot()) +ensures prev.isPublic() +ensures tri.madePublicInv(l.Ctx(), term, tr.getPrev(prev)) +func (l *LabeledLibrary) PublishedTermImpliesMadePublicInvWithSnap(snap tr.TraceEntry, term tm.Term) (prev tr.TraceEntry) { + unfold l.Mem() + prev = l.manager.PublishedTermImpliesMadePublicInvWithSnap(l.ctx, l.owner, snap, term) + fold l.Mem() +} + +ghost +requires l.Mem() +requires (l.Ctx()).eventInv(l.Owner().getPrincipal(), event, l.Snapshot()) +ensures l.Mem() +ensures l.ImmutableState() == old(l.ImmutableState()) +ensures old(l.Snapshot()).isSuffix(l.Snapshot()) +ensures (l.Snapshot()).isEventAt(l.Owner().getPrincipal(), event) +func (l *LabeledLibrary) TriggerEvent(event ev.Event) { + unfold l.Mem() + l.manager.LogEvent(l.ctx, l.owner, event) + fold l.Mem() + assert (l.Snapshot()).isEventAt(l.Owner().getPrincipal(), event) +} +@*/ diff --git a/ReusableVerificationLibrary/labeling/hash-lemmas.gobra b/ReusableVerificationLibrary/labeling/hash-lemmas.gobra new file mode 100644 index 0000000..61fbf4f --- /dev/null +++ b/ReusableVerificationLibrary/labeling/hash-lemmas.gobra @@ -0,0 +1,20 @@ +package labeling + +import "github.com/viperproject/ReusableProtocolVerificationLibrary/label" +import tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" +import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" + + +ghost +decreases +requires ctx.Props() +requires ctx.IsLabeledRelaxed(snapshot, firstT, firstL) +requires ctx.IsLabeledRelaxed(snapshot, secondT, secondL) +ensures ctx.IsLabeledPrecise(snapshot, tm.hash(tm.tuple2(firstT, secondT)), label.Public()) +func (ctx LabelingContext) ApplyHash2Tuple(snapshot tr.TraceEntry, firstT, secondT tm.Term, firstL, secondL label.SecrecyLabel) { + firstActualL := ctx.GetLabel(firstT) + secondActualL := ctx.GetLabel(secondT) + inputT := tm.tuple2(firstT, secondT) + assert ctx.IsValid(snapshot, inputT) + assert ctx.nestedMeet(inputT, getTupleSeq(inputT), 0) == label.Meet(firstActualL, secondActualL) +} diff --git a/ReusableVerificationLibrary/labeling/key-type.gobra b/ReusableVerificationLibrary/labeling/key-type.gobra new file mode 100644 index 0000000..b4d9156 --- /dev/null +++ b/ReusableVerificationLibrary/labeling/key-type.gobra @@ -0,0 +1,47 @@ +package labeling + +type KeyType domain { + // constructors + // type 0 + func KeyTypePke() KeyType + // type 1 + func KeyTypeDh() KeyType + // type 2 + func KeyTypeSigning() KeyType + + // deconstructors + + // WARNING: adapt first axiom if another Term is added! + func getKeyTypeType(KeyType) int + + axiom { // every key type belongs to a known type + forall t KeyType :: { getKeyTypeType(t) } 0 <= getKeyTypeType(t) && getKeyTypeType(t) <= 2 + } + + axiom { + getKeyTypeType(KeyTypePke()) == 0 + } + axiom { // KeyTypePke implies its constructions + forall t KeyType :: { getKeyTypeType(t) } getKeyTypeType(t) == 0 ==> + t == KeyTypePke() + } + + axiom { + getKeyTypeType(KeyTypeDh()) == 1 + } + axiom { // KeyTypeDh implies its constructions + forall t KeyType :: { getKeyTypeType(t) } getKeyTypeType(t) == 1 ==> + t == KeyTypeDh() + } + + axiom { + getKeyTypeType(KeyTypeSigning()) == 2 + } + axiom { // KeyTypeDh implies its constructions + forall t KeyType :: { getKeyTypeType(t) } getKeyTypeType(t) == 2 ==> + t == KeyTypeSigning() + } +} + +decreases +func GetArbKeyType() KeyType diff --git a/ReusableVerificationLibrary/labeling/label-preservation.gobra b/ReusableVerificationLibrary/labeling/label-preservation.gobra new file mode 100644 index 0000000..6c90125 --- /dev/null +++ b/ReusableVerificationLibrary/labeling/label-preservation.gobra @@ -0,0 +1,313 @@ +package labeling + +import arb "github.com/viperproject/ReusableProtocolVerificationLibrary/arbitrary" +import "github.com/viperproject/ReusableProtocolVerificationLibrary/label" +import tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" +import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" + +// in this file, we demonstrate that any derived term can be +// read by a participant given that the participant can read all +// inputs + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, t1, l) +requires ctx.IsMsg(t, t2, l) +ensures ctx.IsMsg(t, tm.tuple2(t1, t2), l) +func (ctx LabelingContext) tuple2(t tr.TraceEntry, t1, t2 tm.Term, l label.SecrecyLabel) { + ctx.IsMsgTupleCreate(t, tm.tuple2(t1, t2), l) +} + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, t1, l) +requires ctx.IsMsg(t, t2, l) +requires ctx.IsMsg(t, t3, l) +ensures ctx.IsMsg(t, tm.tuple3(t1, t2, t3), l) +func (ctx LabelingContext) tuple3(t tr.TraceEntry, t1, t2, t3 tm.Term, l label.SecrecyLabel) { + ctx.IsMsgTupleCreate(t, tm.tuple3(t1, t2, t3), l) +} + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, t1, l) +requires ctx.IsMsg(t, t2, l) +requires ctx.IsMsg(t, t3, l) +requires ctx.IsMsg(t, t4, l) +ensures ctx.IsMsg(t, tm.tuple4(t1, t2, t3, t4), l) +func (ctx LabelingContext) tuple4(t tr.TraceEntry, t1, t2, t3, t4 tm.Term, l label.SecrecyLabel) { + ctx.IsMsgTupleCreate(t, tm.tuple4(t1, t2, t3, t4), l) +} + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, t1, l) +requires ctx.IsMsg(t, t2, l) +requires ctx.IsMsg(t, t3, l) +requires ctx.IsMsg(t, t4, l) +requires ctx.IsMsg(t, t5, l) +ensures ctx.IsMsg(t, tm.tuple5(t1, t2, t3, t4, t5), l) +func (ctx LabelingContext) tuple5(t tr.TraceEntry, t1, t2, t3, t4, t5 tm.Term, l label.SecrecyLabel) { + ctx.IsMsgTupleCreate(t, tm.tuple5(t1, t2, t3, t4, t5), l) +} + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, t1, l) +requires ctx.IsMsg(t, t2, l) +requires ctx.IsMsg(t, t3, l) +requires ctx.IsMsg(t, t4, l) +requires ctx.IsMsg(t, t5, l) +requires ctx.IsMsg(t, t6, l) +requires ctx.IsMsg(t, t7, l) +ensures ctx.IsMsg(t, tm.tuple7(t1, t2, t3, t4, t5, t6, t7), l) +func (ctx LabelingContext) tuple7(t tr.TraceEntry, t1, t2, t3, t4, t5, t6, t7 tm.Term, l label.SecrecyLabel) { + ctx.IsMsgTupleCreate(t, tm.tuple7(t1, t2, t3, t4, t5, t6, t7), l) +} + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, input, l) +ensures ctx.IsMsg(t, tm.hash(input), l) +func (ctx LabelingContext) hash(t tr.TraceEntry, input tm.Term, l label.SecrecyLabel) { + // no body needed +} + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, input, l) +ensures ctx.IsMsg(t, tm.kdf1(input), l) +ensures ctx.IsMsg(t, tm.kdf2(input), l) +ensures ctx.IsMsg(t, tm.kdf3(input), l) +func (ctx LabelingContext) kdf(t tr.TraceEntry, input tm.Term, l label.SecrecyLabel) { + // no body needed +} + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, input, l) +ensures ctx.IsMsg(t, tm.createPk(input), l) +func (ctx LabelingContext) createPk(t tr.TraceEntry, input tm.Term, l label.SecrecyLabel) { + // no body needed +} + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, pk, l) +requires ctx.IsMsg(t, plaintext, l) +// requires that pk and plaintext can be encrypted: +requires ctx.IsValid(t, tm.encrypt(plaintext, pk)) +ensures ctx.IsMsg(t, tm.encrypt(plaintext, pk), l) +func (ctx LabelingContext) encrypt(t tr.TraceEntry, pk, plaintext tm.Term, l label.SecrecyLabel) { + // no body needed +} + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, key, l) +requires ctx.IsMsg(t, nonce, l) +requires ctx.IsMsg(t, plaintext, l) +requires ctx.IsMsg(t, auth, l) +// requires that key, nonce, plaintext, and auth can be encrypted: +requires ctx.IsValid(t, tm.aead(key, nonce, plaintext, auth)) +ensures ctx.IsMsg(t, tm.aead(key, nonce, plaintext, auth), l) +func (ctx LabelingContext) aead(t tr.TraceEntry, key, nonce, plaintext, auth tm.Term, l label.SecrecyLabel) { + // no body needed +} + +ghost +requires ctx.Props() +ensures ctx.IsMsg(t, tm.const1(), l) +func (ctx LabelingContext) const1(t tr.TraceEntry, l label.SecrecyLabel) { + // no body needed +} + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, t1, l) +requires ctx.IsMsg(t, t2, l) +ensures ctx.IsMsg(t, tm.exp(t1, t2), l) +func (ctx LabelingContext) exp(t tr.TraceEntry, t1, t2 tm.Term, l label.SecrecyLabel) { + if t1 == tm.generator() { + if exists e1, e2 tm.Term :: t2 == tm.mult(e1, e2) { + // get witness: + e1 := arb.GetArbTerm() + e2 := arb.GetArbTerm() + assume t2 == tm.mult(e1, e2) + assert ctx.GetLabel(tm.exp(t1, t2)) == label.Join(ctx.GetLabel(e1), ctx.GetLabel(e2)) + } else { + if t2.IsRandom() { + assert ctx.GetLabel(tm.exp(t1, t2)) == label.Public() + } else { + assert ctx.GetLabel(tm.exp(t1, t2)) == ctx.GetLabel(t2) + } + } + } else { + assert t1 != tm.generator() + if exists e1 tm.Term :: { tm.exp(tm.generator(), e1) } t1 == tm.exp(tm.generator(), e1) { + // get witness: + e1 := arb.GetArbTerm() + assume t1 == tm.exp(tm.generator(), e1) + assert ctx.GetLabel(tm.exp(t1, t2)) == label.Join(ctx.GetLabel(e1), ctx.GetLabel(t2)) + ctx.CanFlowCreateJoinLhs(t, ctx.GetLabel(e1), ctx.GetLabel(t2), l) + } else { + assert ctx.GetLabel(tm.exp(t1, t2)) == label.Meet(ctx.GetLabel(t1), ctx.GetLabel(t2)) + ctx.CanFlowCreateMeetLhs(t, ctx.GetLabel(t1), ctx.GetLabel(t2), l) + } + } +} + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, t1, l) +requires ctx.IsMsg(t, t2, l) +ensures ctx.IsMsg(t, tm.mult(t1, t2), l) +func (ctx LabelingContext) mult(t tr.TraceEntry, t1, t2 tm.Term, l label.SecrecyLabel) { + ctx.CanFlowCreateJoinLhs(t, ctx.GetLabel(t1), ctx.GetLabel(t2), l) +} + +ghost +requires ctx.Props() +ensures ctx.IsMsg(t, tm.stringTerm(s), l) +func (ctx LabelingContext) stringTerm(t tr.TraceEntry, s string, l label.SecrecyLabel) { + // no body needed +} + +ghost +requires ctx.Props() +ensures ctx.IsMsg(t, tm.zeroString(n), l) +func (ctx LabelingContext) zeroString(t tr.TraceEntry, n int, l label.SecrecyLabel) { + // no body needed +} + +ghost +requires ctx.Props() +ensures ctx.IsMsg(t, tm.integer64(n), l) +func (ctx LabelingContext) integer64(t tr.TraceEntry, n uint64, l label.SecrecyLabel) { + // no body needed +} + +ghost +requires ctx.Props() +ensures ctx.IsMsg(t, tm.integer32(n), l) +func (ctx LabelingContext) integer32(t tr.TraceEntry, n uint32, l label.SecrecyLabel) { + // no body needed +} + +ghost +requires ctx.Props() +ensures ctx.IsMsg(t, tm.infoTerm(), l) +func (ctx LabelingContext) infoTerm(t tr.TraceEntry, l label.SecrecyLabel) { + // no body needed +} + +ghost +requires ctx.Props() +ensures ctx.IsMsg(t, tm.prologueTerm(), l) +func (ctx LabelingContext) prologueTerm(t tr.TraceEntry, l label.SecrecyLabel) { + // no body needed +} + +ghost +requires ctx.Props() +ensures ctx.IsMsg(t, tm.generator(), l) +func (ctx LabelingContext) generator(t tr.TraceEntry, l label.SecrecyLabel) { + // no body needed +} + + +// similarly to the constructor above, we show similar lemmas for deconstructors. +// note however that we only show label preservation for deconstructors that match +// an implementation. For example, we do not show label preservation for deconstructing +// a hash since such a method does not exist in practice. +ghost +requires ctx.Props() +requires ctx.IsMsg(t, tm.tuple2(t1, t2), l) +ensures ctx.IsMsg(t, t1, l) +ensures ctx.IsMsg(t, t2, l) +func (ctx LabelingContext) untuple2(t tr.TraceEntry, t1, t2 tm.Term, l label.SecrecyLabel) { + ctx.IsMsgTupleResolve(t, tm.tuple2(t1, t2), l) +} + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, tm.tuple3(t1, t2, t3), l) +ensures ctx.IsMsg(t, t1, l) +ensures ctx.IsMsg(t, t2, l) +ensures ctx.IsMsg(t, t3, l) +func (ctx LabelingContext) untuple3(t tr.TraceEntry, t1, t2, t3 tm.Term, l label.SecrecyLabel) { + ctx.IsMsgTupleResolve(t, tm.tuple3(t1, t2, t3), l) +} + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, tm.tuple4(t1, t2, t3, t4), l) +ensures ctx.IsMsg(t, t1, l) +ensures ctx.IsMsg(t, t2, l) +ensures ctx.IsMsg(t, t3, l) +ensures ctx.IsMsg(t, t4, l) +func (ctx LabelingContext) untuple4(t tr.TraceEntry, t1, t2, t3, t4 tm.Term, l label.SecrecyLabel) { + ctx.IsMsgTupleResolve(t, tm.tuple4(t1, t2, t3, t4), l) +} + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, tm.tuple5(t1, t2, t3, t4, t5), l) +ensures ctx.IsMsg(t, t1, l) +ensures ctx.IsMsg(t, t2, l) +ensures ctx.IsMsg(t, t3, l) +ensures ctx.IsMsg(t, t4, l) +ensures ctx.IsMsg(t, t5, l) +func (ctx LabelingContext) untuple5(t tr.TraceEntry, t1, t2, t3, t4, t5 tm.Term, l label.SecrecyLabel) { + ctx.IsMsgTupleResolve(t, tm.tuple5(t1, t2, t3, t4, t5), l) +} + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, tm.tuple7(t1, t2, t3, t4, t5, t6, t7), l) +ensures ctx.IsMsg(t, t1, l) +ensures ctx.IsMsg(t, t2, l) +ensures ctx.IsMsg(t, t3, l) +ensures ctx.IsMsg(t, t4, l) +ensures ctx.IsMsg(t, t5, l) +ensures ctx.IsMsg(t, t6, l) +ensures ctx.IsMsg(t, t7, l) +func (ctx LabelingContext) untuple7(t tr.TraceEntry, t1, t2, t3, t4, t5, t6, t7 tm.Term, l label.SecrecyLabel) { + ctx.IsMsgTupleResolve(t, tm.tuple7(t1, t2, t3, t4, t5, t6, t7), l) +} + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, tm.encrypt(plaintext, tm.createPk(sk)), l) +requires ctx.IsMsg(t, sk, l) // knowing the secret key is a requirement for decryption +ensures ctx.IsMsg(t, plaintext, l) +func (ctx LabelingContext) decrypt(t tr.TraceEntry, plaintext, sk tm.Term, l label.SecrecyLabel) { + pk := tm.createPk(sk) + plaintextLabel := ctx.GetLabel(plaintext) + skLabel := ctx.GetLabel(sk) + // the following assertion is necessary to derive that `ctx.IsMsg(t, msg, skLabel)` + assert ctx.IsValidEncrypt(t, pk, plaintext, plaintextLabel) + ctx.CanFlowTransitive(t, plaintextLabel, skLabel, l) +} + +ghost +requires ctx.Props() +requires ctx.IsMsg(t, tm.aead(key, nonce, plaintext, ad), l) +requires ctx.IsMsg(t, key, l) // knowing the key is a requirement for decryption +ensures ctx.IsMsg(t, nonce, l) +ensures ctx.IsMsg(t, plaintext, l) +ensures ctx.IsMsg(t, ad, l) +func (ctx LabelingContext) AeadDecrypt(t tr.TraceEntry, key, nonce, plaintext, ad tm.Term, l label.SecrecyLabel) { + keyL := ctx.GetLabel(key) + plaintextL := ctx.GetLabel(plaintext) + assert ctx.IsValidAead(t, key, nonce, plaintext, plaintextL, ad) + ctx.FlowsToPublicCanFlow(t, ctx.GetLabel(nonce), l) + ctx.FlowsToPublicCanFlow(t, ctx.GetLabel(ad), l) + if ctx.CanFlow(t, plaintextL, label.Public()) && + ctx.CanFlow(t, keyL, label.Public()) { + ctx.FlowsToPublicCanFlow(t, plaintextL, l) + ctx.FlowsToPublicCanFlow(t, keyL, l) + } else { + ctx.CanFlowTransitive(t, plaintextL, keyL, l) + } +} diff --git a/ReusableVerificationLibrary/labeling/labeling.gobra b/ReusableVerificationLibrary/labeling/labeling.gobra new file mode 100644 index 0000000..0aefd6d --- /dev/null +++ b/ReusableVerificationLibrary/labeling/labeling.gobra @@ -0,0 +1,2351 @@ +package labeling + +import arb "github.com/viperproject/ReusableProtocolVerificationLibrary/arbitrary" +import ev "github.com/viperproject/ReusableProtocolVerificationLibrary/event" +import "github.com/viperproject/ReusableProtocolVerificationLibrary/label" +import p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" +import tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" +import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" +import u "github.com/viperproject/ReusableProtocolVerificationLibrary/usage" +import . "github.com/viperproject/ReusableProtocolVerificationLibrary/usagecontext" + + +/** alias */ +type IdSet = set[p.Id] + +/** + * overview over some lemmas available in this file on the level of `canFlowInternal_DYStar + * ghost + * pure canFlowInternal_DYStar(corruptIds IdSet, l1, l2 label.SecrecyLabel) bool + * + * ghost + * ensures canFlowInternal_DYStar(corruptIds, l, l) + * canFlowInternalReflexive_DYStar(corruptIds IdSet, l label.SecrecyLabel) + * + * ghost + * ensures (canFlowInternal_DYStar(corruptIds, l1, l2) && canFlowInternal_DYStar(corruptIds, l2, l3)) ==> canFlowInternal_DYStar(corruptIds, l1, l3) + * canFlowInternalTransitive_DYStar(corruptIds IdSet, l1, l2, l3 label.SecrecyLabel) + * + * ghost + * ensures canFlowInternal_DYStar(corruptIds, l1, label.Public()) ==> canFlowInternal_DYStar(corruptIds, l1, l2) + * flowsToPublicCanFlowInternal_DYStar(corruptIds IdSet, l1, l2 label.SecrecyLabel) + * + * ghost + * ensures (canFlowInternal_DYStar(corruptIds, l2, label.Public()) && canFlowInternal_DYStar(corruptIds, l1, l2)) ==> canFlowInternal_DYStar(corruptIds, l1, label.Public()) + * canFlowFlowsToPublic_DYStar(corruptIds IdSet, l1, l2 label.SecrecyLabel) + * + * ghost + * requires l1.IsReaders() && l2.IsReaders() + * requires label.GetReaders(l2) subset label.GetReaders(l1) + * ensures canFlowInternal_DYStar(corruptIds, l1, l2) + * canFlowInternalToSubsetReaders_DYStar(corruptIds IdSet, l1, l2 label.SecrecyLabel) + * + * ghost + * requires canFlowInternal_DYStar(corruptIds, l1, l3) + * requires canFlowInternal_DYStar(corruptIds, l2, l3) + * ensures canFlowInternal_DYStar(corruptIds, label.Meet(l1, l2), l3) + * canFlowInternalCreateMeetLhs_DYStar(corruptIds IdSet, l1, l2, l3 label.SecrecyLabel) + * + * ghost + * requires canFlowInternal_DYStar(corruptIds, label.Meet(l1, l2), l3) + * ensures canFlowInternal_DYStar(corruptIds, l1, l3) + * ensures canFlowInternal_DYStar(corruptIds, l2, l3) + * canFlowInternalResolveMeetLhs_DYStar(corruptIds IdSet, l1, l2, l3 label.SecrecyLabel) + * + * ghost + * requires canFlowInternal_DYStar(corruptIds, l1, l2) || canFlowInternal_DYStar(corruptIds, l1, l3) + * ensures canFlowInternal_DYStar(corruptIds, l1, label.Meet(l2, l3)) + * canFlowInternalCreateMeetRhs_DYStar(corruptIds IdSet, l1, l2, l3 label.SecrecyLabel) + * + * ghost + * requires canFlowInternal_DYStar(corruptIds, l1, label.Meet(l2, l3)) + * requires canFlowInternal_DYStar(corruptIds, l2, l3) + * ensures canFlowInternal_DYStar(corruptIds, l1, l3) + * canFlowInternalResolveMeetRhs_DYStar(corruptIds IdSet, l1, l2, l3 label.SecrecyLabel) + * + * ghost + * requires canFlowInternal_DYStar(corruptIds, l1, l3) || canFlowInternal_DYStar(corruptIds, l2, l3) + * ensures canFlowInternal_DYStar(corruptIds, label.Join(l1, l2), l3) + * canFlowInternalCreateJoinLhs_DYStar(corruptIds IdSet, l1, l2, l3 label.SecrecyLabel) + * + * ghost + * requires canFlowInternal_DYStar(corruptIds, label.Join(l1, l1), l2) + * ensures canFlowInternal_DYStar(corruptIds, l1, l2) + * canFlowInternalResolveJoinLhs_DYStar(corruptIds IdSet, l1, l2 label.SecrecyLabel) + * + * ghost + * requires canFlowInternal_DYStar(corruptIds, l1, l2) + * requires canFlowInternal_DYStar(corruptIds, l1, l3) + * ensures canFlowInternal_DYStar(corruptIds, l1, label.Join(l2, l3)) + * canFlowInternalCreateJoinRhs_DYStar(corruptIds IdSet, l1, l2, l3 label.SecrecyLabel) + * + * ghost + * requires canFlowInternal_DYStar(corruptIds, l1, label.Join(l2, l3)) + * ensures canFlowInternal_DYStar(corruptIds, l1, l2) + * ensures canFlowInternal_DYStar(corruptIds, l1, l3) + * canFlowInternalResolveJoinRhs_DYStar(corruptIds IdSet, l1, l2, l3 label.SecrecyLabel) + * + * ghost + * requires canFlowInternal_DYStar(corruptIds, l1, label.Meet(l2, l3)) + * requires l2FlowsToPublic ==> canFlowInternal_DYStar(corruptIds, l2, label.Public()) + * requires !l2FlowsToPublic ==> canFlowInternal_DYStar(corruptIds, l3, label.Public()) + * ensures l2FlowsToPublic ==> canFlowInternal_DYStar(corruptIds, l1, l3) + * ensures !l2FlowsToPublic ==> canFlowInternal_DYStar(corruptIds, l1, l2) + * canFlowInternalResolveMeetPublicRhs_DYStar(corruptIds IdSet, l1, l2, l3 label.SecrecyLabel, l2FlowsToPublic bool) + * + * ghost + * requires canFlowInternal_DYStar(corruptIds, l1, l2) + * requires canFlowInternal_DYStar(corruptIds, l3, l4) + * ensures canFlowInternal_DYStar(corruptIds, label.Meet(l1, l3), label.Meet(l2, l4)) + * canFlowInternalCreateMeetBoth_DYStar(corruptIds IdSet, l1, l2, l3, l4 label.SecrecyLabel) + * + * ghost + * requires canFlowInternal_DYStar(corruptIds, l1, l2) + * requires canFlowInternal_DYStar(corruptIds, l3, l4) + * ensures canFlowInternal_DYStar(corruptIds, label.Join(l1, l3), label.Join(l2, l4)) + * canFlowInternalCreateJoinBoth_DYStar(corruptIds IdSet, l1, l2, l3, l4 label.SecrecyLabel) + */ + +type LabelingContext struct { + usage UsageContext +} + +ghost +decreases +pure func (ctx LabelingContext) Props() bool { + return ctx.usage != nil +} + +// TODO make ghost +decreases +pure func GetLabelingContext(usage UsageContext) (res LabelingContext) { + return LabelingContext{ usage } +} + +ghost +decreases tm.getTermHeight(term) +requires ctx.Props() +pure func (ctx LabelingContext) IsValid(t tr.TraceEntry, term tm.Term) bool { + return (term.IsInteger64() ==> true) && + (term.IsInteger32() ==> true) && + (term.IsConst1() ==> true) && + (term.IsString() ==> true) && + (term.IsZeroString() ==> true) && + (term.IsInfo() ==> true) && + (term.IsPrologue() ==> true) && + (term.IsGenerator() ==> true) && + (term.IsPk() ==> ctx.IsValid(t, tm.getSk(term))) && + (term.IsEncrypt() ==> + ctx.IsValidEncryptInternal(t, tm.getPk(term), tm.getPlaintext(term), ctx.GetLabel(tm.getPlaintext(term)), true)) && + (term.IsAead() ==> + ctx.IsValidAeadInternal(t, tm.getAeadKey(term), tm.getAeadNonce(term), tm.getPlaintext(term), ctx.GetLabel(tm.getPlaintext(term)), tm.getAuthtext(term), true)) && + (term.IsRandom() ==> + // `nonceOccurs` saves us from writing yet another function to simply state that `term` is on the trace + t.OnlyNonceOccurs(term)) && + (term.IsExp() ==> exists base, e tm.Term :: { tm.exp(base, e) } term == tm.exp(base, e) && ctx.IsValid(t, base) && ctx.IsValid(t, e)) && + ((term.IsHash() || term.IsKdf1() || term.IsKdf2() || term.IsKdf3()) ==> ctx.IsValid(t, tm.getInput(term))) && + (term.IsTuple() ==> forall i int :: { tm.getTupleElem(term, i) } 0 <= i && i < term.GetTupleArity() ==> ctx.IsValid(t, tm.getTupleElem(term, i))) && + (term.IsSignature() ==> + ctx.IsValidSignatureInternal(t, tm.getPlaintext(term), tm.getSk(term), true)) +} + +ghost +decreases +requires ctx.Props() +requires ctx.GetLabel(plaintext) == plaintextLabel +pure func (ctx LabelingContext) IsValidEncrypt(t tr.TraceEntry, pk, plaintext tm.Term, plaintextLabel label.SecrecyLabel) bool { + return ctx.IsValidEncryptInternal(t, pk, plaintext, plaintextLabel, true) +} + +ghost +decreases tm.getTermHeight(tm.encrypt(plaintext, pk)), mutualRecursionTrick +requires ctx.Props() +requires ctx.GetLabel(plaintext) == plaintextLabel +/** + * `IsValidEncryptInternal` is used with a fake argument to over-come a common limitation in proving termination for + * mutally recursive calls. `IsValidEncrypt` provides a wrapper that hides these internal details. + */ +pure func (ctx LabelingContext) IsValidEncryptInternal(t tr.TraceEntry, pk, plaintext tm.Term, plaintextLabel label.SecrecyLabel, mutualRecursionTrick bool) bool { + return ctx.IsValid(t, pk) && + ctx.IsValid(t, plaintext) && + // message has to flow to secrecy label of secret key + ctx.CanFlow(t, plaintextLabel, ctx.GetSkLabel(pk)) && + ( + // either the plaintext is public ... + ctx.CanFlow(t, plaintextLabel, label.Public()) || + // ... or pkePred holds + ( + pk.IsPk() && + ctx.IsPkeKey(tm.getSk(pk)) && + ctx.usage.PkePred(t, u.GetUsageString(get(ctx.GetUsage(tm.getSk(pk)))), plaintext, pk))) +} + +ghost +decreases +requires ctx.Props() +requires ctx.GetLabel(plaintext) == plaintextLabel +pure func (ctx LabelingContext) IsValidAead(t tr.TraceEntry, key, nonce, plaintext tm.Term, plaintextLabel label.SecrecyLabel, authtext tm.Term) bool { + return ctx.IsValidAeadInternal(t, key, nonce, plaintext, plaintextLabel, authtext, true) +} + +ghost +decreases tm.getTermHeight(tm.aead(key, nonce, plaintext, authtext)), mutualRecursionTrick +requires ctx.Props() +requires ctx.GetLabel(plaintext) == plaintextLabel +/** + * `IsValidAeadInternal` is used with a fake argument to over-come a common limitation in proving termination for + * mutally recursive calls. `IsValidAead` provides a wrapper that hides these internal details. + */ +pure func (ctx LabelingContext) IsValidAeadInternal(t tr.TraceEntry, key, nonce, plaintext tm.Term, plaintextLabel label.SecrecyLabel, authtext tm.Term, mutualRecursionTrick bool) bool { + return ctx.IsValid(t, key) && + ctx.IsValid(t, nonce) && + ctx.IsValid(t, plaintext) && + ctx.IsValid(t, authtext) && + // nonce and authtext have to flow to public + ctx.CanFlow(t, ctx.GetLabel(nonce), label.Public()) && + ctx.CanFlow(t, ctx.GetLabel(authtext), label.Public()) && + ( + // either key and message are public... + (ctx.CanFlow(t, ctx.GetLabel(key), label.Public()) && + ctx.CanFlow(t, plaintextLabel, label.Public())) || + // ... or message can flow to key's label and AeadPred holds + (ctx.CanFlow(t, plaintextLabel, ctx.GetLabel(key)) && + ctx.HasAeadKeyUsage(key) && + ctx.usage.AeadPred(t, u.GetUsageString(get(ctx.GetUsage(key))), key, nonce, plaintext, authtext))) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) IsValidSignature(t tr.TraceEntry, msg, sk tm.Term) bool { + return ctx.IsValidSignatureInternal(t, msg, sk, true) +} + +ghost +decreases tm.getTermHeight(tm.sign(msg, sk)), mutualRecursionTrick +requires ctx.Props() +pure func (ctx LabelingContext) IsValidSignatureInternal(t tr.TraceEntry, msg, sk tm.Term, mutualRecursionTrick bool) bool { + return ctx.IsValid(t, msg) && + ctx.IsValid(t, sk) && + ctx.CanFlow(t, ctx.GetLabel(msg), label.Public()) && // since msg can be extracted from signature + ( + // either the secret key is public ... + ctx.CanFlow(t, ctx.GetLabel(sk), label.Public()) || + // ... or SignPred holds + ( + ctx.IsSigningKey(sk) && + ctx.usage.SignPred(t, u.GetUsageString(get(ctx.GetUsage(sk))), msg, sk))) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) IsAeadKey(t tr.TraceEntry, key tm.Term, keyL label.SecrecyLabel, usageString string) bool { + return ctx.IsSecretRelaxed(t, key, keyL, u.AeadKey(usageString)) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) IsSignKey(t tr.TraceEntry, key tm.Term, skOwner p.Id, usageString string) bool { + return ctx.IsSecretRelaxed(t, key, label.Readers(set[p.Id]{ skOwner }), u.SigningKey(usageString)) +} + +ghost +decreases tm.getTermHeight(term) +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsValid(t1, term) +ensures ctx.IsValid(t2, term) +func (ctx LabelingContext) IsValidMonotonic(t1, t2 tr.TraceEntry, term tm.Term) { + if (term.IsRandom()) { + t1.OnlyNonceOccursMonotonic(t2, term) + } else if (term.IsPk()) { + ctx.IsValidMonotonic(t1, t2, tm.getSk(term)) + } else if (term.IsEncrypt()) { + pk := tm.getPk(term) + plaintext := tm.getPlaintext(term) + ctx.IsValidEncryptMonotonic(t1, t2, pk, plaintext, ctx.GetLabel(plaintext), true) + } else if term.IsAead() { + key := tm.getAeadKey(term) + nonce := tm.getAeadNonce(term) + plaintext := tm.getPlaintext(term) + authtext := tm.getAuthtext(term) + ctx.IsValidAeadMonotonic(t1, t2, key, nonce, plaintext, ctx.GetLabel(plaintext), authtext, true) + } else if term.IsExp() { + assert exists base, e tm.Term :: { tm.exp(base, e) } term == tm.exp(base, e) && ctx.IsValid(t1, base) && ctx.IsValid(t1, e) + base, e := arb.GetArbTerm(), arb.GetArbTerm() + assume term == tm.exp(base, e) && ctx.IsValid(t1, base) && ctx.IsValid(t1, e) + ctx.IsValidMonotonic(t1, t2, base) + ctx.IsValidMonotonic(t1, t2, e) + } else if (term.IsHash() || term.IsKdf1() || term.IsKdf2() || term.IsKdf3()) { + ctx.IsValidMonotonic(t1, t2, tm.getInput(term)) + } else if term.IsTuple() { + invariant 0 <= i && i <= term.GetTupleArity() + invariant forall j int :: { tm.getTupleElem(term, j) } 0 <= j && j < term.GetTupleArity() ==> ctx.IsValid(t1, tm.getTupleElem(term, j)) + invariant forall j int :: { tm.getTupleElem(term, j) } 0 <= j && j < i ==> ctx.IsValid(t2, tm.getTupleElem(term, j)) + decreases term.GetTupleArity() - i + for i := 0; i < term.GetTupleArity(); i++ { + ctx.IsValidMonotonic(t1, t2, tm.getTupleElem(term, i)) + } + } else if term.IsSignature() { + sk := tm.getSk(term) + plaintext := tm.getPlaintext(term) + ctx.IsValidSignatureMonotonic(t1, t2, plaintext, sk, true) + } +} + +ghost +decreases tm.getTermHeight(tm.encrypt(plaintext, pk)), mutualRecursionTrick +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.GetLabel(plaintext) == plaintextLabel +requires ctx.IsValidEncrypt(t1, pk, plaintext, plaintextLabel) +ensures ctx.IsValidEncrypt(t2, pk, plaintext, plaintextLabel) +func (ctx LabelingContext) IsValidEncryptMonotonic(t1, t2 tr.TraceEntry, pk, plaintext tm.Term, plaintextLabel label.SecrecyLabel, mutualRecursionTrick bool) { + ctx.IsValidMonotonic(t1, t2, pk) + ctx.IsValidMonotonic(t1, t2, plaintext) + skLabel := ctx.GetSkLabel(pk) + ctx.CanFlowMonotonic(t1, t2, plaintextLabel, skLabel) + if (ctx.CanFlow(t1, plaintextLabel, label.Public())) { + ctx.CanFlowMonotonic(t1, t2, plaintextLabel, label.Public()) + } else { + ctx.usage.PkePredMonotonic(t1, t2, u.GetUsageString(get(ctx.GetUsage(tm.getSk(pk)))), plaintext, pk) + } +} + +ghost +decreases tm.getTermHeight(tm.aead(key, nonce, plaintext, authtext)), mutualRecursionTrick +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.GetLabel(plaintext) == plaintextLabel +requires ctx.IsValidAead(t1, key, nonce, plaintext, plaintextLabel, authtext) +ensures ctx.IsValidAead(t2, key, nonce, plaintext, plaintextLabel, authtext) +func (ctx LabelingContext) IsValidAeadMonotonic(t1, t2 tr.TraceEntry, key, nonce, plaintext tm.Term, plaintextLabel label.SecrecyLabel, authtext tm.Term, mutualRecursionTrick bool) { + ctx.IsValidMonotonic(t1, t2, key) + ctx.IsValidMonotonic(t1, t2, nonce) + ctx.IsValidMonotonic(t1, t2, plaintext) + ctx.IsValidMonotonic(t1, t2, authtext) + ctx.CanFlowMonotonic(t1, t2, ctx.GetLabel(nonce), label.Public()) + ctx.CanFlowMonotonic(t1, t2, ctx.GetLabel(authtext), label.Public()) + if (ctx.CanFlow(t1, ctx.GetLabel(key), label.Public()) && + ctx.CanFlow(t1, plaintextLabel, label.Public())) { + ctx.CanFlowMonotonic(t1, t2, ctx.GetLabel(key), label.Public()) + ctx.CanFlowMonotonic(t1, t2, plaintextLabel, label.Public()) + } else { + ctx.CanFlowMonotonic(t1, t2, plaintextLabel, ctx.GetLabel(key)) + usageCtx := ctx.GetUsage(key) + ctx.usage.AeadPredMonotonic(t1, t2, u.GetUsageString(get(usageCtx)), key, nonce, plaintext, authtext) + } +} + +ghost +decreases tm.getTermHeight(tm.sign(msg, sk)), mutualRecursionTrick +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsValidSignature(t1, msg, sk) +ensures ctx.IsValidSignature(t2, msg, sk) +func (ctx LabelingContext) IsValidSignatureMonotonic(t1, t2 tr.TraceEntry, msg, sk tm.Term, mutualRecursionTrick bool) { + ctx.IsValidMonotonic(t1, t2, msg) + ctx.IsValidMonotonic(t1, t2, sk) + ctx.CanFlowMonotonic(t1, t2, ctx.GetLabel(msg), label.Public()) + if ctx.CanFlow(t1, ctx.GetLabel(sk), label.Public()) { + ctx.CanFlowMonotonic(t1, t2, ctx.GetLabel(sk), label.Public()) + } else { + usageCtx := ctx.GetUsage(sk) + ctx.usage.SignPredMonotonic(t1, t2, u.GetUsageString(get(usageCtx)), msg, sk) + } +} + +ghost +decreases +requires ctx.Props() +/** expresses that a term is valid and that its label is `l` */ +pure func (ctx LabelingContext) IsLabeled(t tr.TraceEntry, term tm.Term, l label.SecrecyLabel) bool { + return ctx.IsValid(t, term) && + ctx.GetLabel(term) == l +} + +ghost +decreases +requires ctx.Props() +/** expresses that a term is valid and that `l` flows to its label */ +pure func (ctx LabelingContext) IsLabeledRelaxed(t tr.TraceEntry, term tm.Term, l label.SecrecyLabel) bool { + return ctx.IsValid(t, term) && + ctx.CanFlow(t, l, ctx.GetLabel(term)) +} + +ghost +decreases +requires ctx.Props() +/** expresses that a term is valid and that `l` flows to its label and back */ +pure func (ctx LabelingContext) IsLabeledPrecise(t tr.TraceEntry, term tm.Term, l label.SecrecyLabel) bool { + return ctx.IsValid(t, term) && + ctx.CanFlow(t, l, ctx.GetLabel(term)) && + ctx.CanFlow(t, ctx.GetLabel(term), l) +} + +ghost +decreases +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsLabeled(t1, term, l) +ensures ctx.IsLabeled(t2, term, l) +func (ctx LabelingContext) IsLabeledMonotonic(t1, t2 tr.TraceEntry, term tm.Term, l label.SecrecyLabel) { + ctx.IsValidMonotonic(t1, t2, term) +} + +ghost +decreases +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsLabeledRelaxed(t1, term, l) +ensures ctx.IsLabeledRelaxed(t2, term, l) +func (ctx LabelingContext) IsLabeledRelaxedMonotonic(t1, t2 tr.TraceEntry, term tm.Term, l label.SecrecyLabel) { + ctx.IsValidMonotonic(t1, t2, term) + ctx.CanFlowMonotonic(t1, t2, l, ctx.GetLabel(term)) +} + +ghost +decreases +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsLabeledPrecise(t1, term, l) +ensures ctx.IsLabeledPrecise(t2, term, l) +func (ctx LabelingContext) IsLabeledPreciseMonotonic(t1, t2 tr.TraceEntry, term tm.Term, l label.SecrecyLabel) { + ctx.IsValidMonotonic(t1, t2, term) + ctx.CanFlowMonotonic(t1, t2, l, ctx.GetLabel(term)) + ctx.CanFlowMonotonic(t1, t2, ctx.GetLabel(term), l) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) IsSecret(t tr.TraceEntry, term tm.Term, l label.SecrecyLabel, usage u.Usage) bool { + return ctx.IsLabeled(t, term, l) && + ctx.GetUsage(term) == some(usage) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) IsSecretRelaxed(t tr.TraceEntry, term tm.Term, l label.SecrecyLabel, usage u.Usage) bool { + return ctx.IsLabeledRelaxed(t, term, l) && + ctx.GetUsage(term) == some(usage) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) IsSecretPrecise(t tr.TraceEntry, term tm.Term, l label.SecrecyLabel, usage u.Usage) bool { + return ctx.IsLabeledPrecise(t, term, l) && + ctx.GetUsage(term) == some(usage) +} + +ghost +decreases +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsSecret(t1, term, l, usage) +ensures ctx.IsSecret(t2, term, l, usage) +func (ctx LabelingContext) IsSecretMonotonic(t1, t2 tr.TraceEntry, term tm.Term, l label.SecrecyLabel, usage u.Usage) { + ctx.IsLabeledMonotonic(t1, t2, term, l) +} + +ghost +decreases +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsSecretRelaxed(t1, term, l, usage) +ensures ctx.IsSecretRelaxed(t2, term, l, usage) +func (ctx LabelingContext) IsSecretRelaxedMonotonic(t1, t2 tr.TraceEntry, term tm.Term, l label.SecrecyLabel, usage u.Usage) { + ctx.IsLabeledRelaxedMonotonic(t1, t2, term, l) +} + +ghost +decreases tm.getTermHeight(term) +requires ctx.Props() +ensures term.IsInteger64() ==> res == label.Public() +ensures term.IsInteger32() ==> res == label.Public() +ensures term.IsConst1() ==> res == label.Public() +ensures term.IsString() ==> res == label.Public() +ensures term.IsZeroString() ==> res == label.Public() +ensures term.IsInfo() ==> res == label.Public() +ensures term.IsPrologue() ==> res == label.Public() +ensures term.IsGenerator() ==> res == label.Public() +ensures term.IsPk() ==> res == label.Public() +ensures term.IsEncrypt() ==> res == label.Public() +ensures term.IsAead() ==> res == label.Public() +ensures term.IsRandom() ==> res == tm.getRandomLabel(term) +// hash, kdf1, kdf2, and kdf3 are considered one-way functions +// hashes are considered to be fine for sending them to the network +// the result of applying a KDF function however is used as a key and therefore, we want to have the +// tightest label that is possible, which is simply the label of its parameter. +// any more generic label (e.g. even public()) would still be sound but we weaken any secrecy property +// as we would consider an unnecessarily big set of participants of which any could be corrupted and +// thus the secrecy lemma holds trivially +ensures term.IsHash() ==> res == label.Public() +ensures (term.IsKdf1() || term.IsKdf2() || term.IsKdf3()) ==> res == ctx.GetLabel(tm.getInput(term)) +ensures term.IsTuple() ==> res == ctx.nestedMeetInternal(term, getTupleSeq(term), 0, true) // intersection of audience is allowed to read +// DH exponentiation is special cased: +// if a term is `(g^x)^y` or equivalently `g^(x*y)` it results in the join label of x and y +// i.e. the DH key can be read by all readers of x and y (set union) since it's sufficient +// to either know x or y as the public key is public. +// note that one must make sure the postconditions to not specify multiple labels for the same term, which +// would immediately be a contradiction. Thus, the case where t1 is an exponent of the generator and another term `e1` has to be explicitly excluded +// as this case is already covered via `mult`, i.e. (g^e1)^t2 == g^(e1 * t2) +ensures forall t1, t2 tm.Term :: { tm.mult(t1, t2) } term == tm.mult(t1, t2) ==> res == label.Join(ctx.GetLabel(t1), ctx.GetLabel(t2)) +ensures forall t1, t2 tm.Term :: { tm.exp(t1, t2) } term == tm.exp(t1, t2) ==> + (t1.IsGenerator() && t2.IsRandom() ? res == label.Public() : + t1.IsGenerator() /* && !t2.IsRandom() */ ? res == ctx.GetLabel(t2) : + !(exists e1 tm.Term :: { tm.exp(tm.generator(), e1) } t1 == tm.exp(tm.generator(), e1)) ==> res == label.Meet(ctx.GetLabel(t1), ctx.GetLabel(t2))) +ensures forall msg, sk tm.Term :: { tm.sign(msg, sk) } term == tm.sign(msg, sk) ==> res == ctx.GetLabel(msg) +/** + * WARNING: + * 2 terms that are equal based on the equational theory must return the same label! + */ +pure func (ctx LabelingContext) GetLabel(term tm.Term) (res label.SecrecyLabel) + + +ghost +decreases +requires t.IsTuple() +ensures len(res) == t.GetTupleArity() +ensures forall i int :: 0 <= i && i < t.GetTupleArity() ==> res[i] == tm.getTupleElem(t, i) && tm.getTermHeight(res[i]) < tm.getTermHeight(t) +pure func getTupleSeq(t tm.Term) (res seq[tm.Term]) { + return t.IsTuple2() ? seq[tm.Term]{ tm.getTupleElem(t, 0), tm.getTupleElem(t, 1) } : + t.IsTuple3() ? seq[tm.Term]{ tm.getTupleElem(t, 0), tm.getTupleElem(t, 1), tm.getTupleElem(t, 2) } : + t.IsTuple4() ? seq[tm.Term]{ tm.getTupleElem(t, 0), tm.getTupleElem(t, 1), tm.getTupleElem(t, 2), tm.getTupleElem(t, 3) } : + t.IsTuple5() ? seq[tm.Term]{ tm.getTupleElem(t, 0), tm.getTupleElem(t, 1), tm.getTupleElem(t, 2), tm.getTupleElem(t, 3), tm.getTupleElem(t, 4) } : + // t.IsTuple6() ? seq[tm.Term]{ tm.getTupleElem(t, 0), tm.getTupleElem(t, 1), tm.getTupleElem(t, 2), tm.getTupleElem(t, 3), tm.getTupleElem(t, 4), tm.getTupleElem(t, 5) } : + seq[tm.Term]{ tm.getTupleElem(t, 0), tm.getTupleElem(t, 1), tm.getTupleElem(t, 2), tm.getTupleElem(t, 3), tm.getTupleElem(t, 4), tm.getTupleElem(t, 5), tm.getTupleElem(t, 6) } +} + +ghost +decreases +requires ctx.Props() && t.IsTuple() && terms == getTupleSeq(t) +requires 0 <= startIdx && startIdx + 2 <= len(terms) +pure func (ctx LabelingContext) nestedMeet(t tm.Term, terms seq[tm.Term], startIdx int) label.SecrecyLabel { + return ctx.nestedMeetInternal(t, terms, startIdx, true) +} + +ghost +decreases tm.getTermHeight(t), len(terms) - startIdx, mutualRecursionTrick +requires ctx.Props() && t.IsTuple() && terms == getTupleSeq(t) +requires 0 <= startIdx && startIdx + 2 <= len(terms) +pure func (ctx LabelingContext) nestedMeetInternal(t tm.Term, terms seq[tm.Term], startIdx int, mutualRecursionTrick bool) label.SecrecyLabel { + return startIdx + 2 == len(terms) ? label.Meet(ctx.GetLabel(terms[startIdx]), ctx.GetLabel(terms[startIdx + 1])) : + label.Meet(ctx.GetLabel(terms[startIdx]), ctx.nestedMeetInternal(t, terms, startIdx + 1, mutualRecursionTrick)) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) GetUsage(t tm.Term) option[u.Usage] { + return ctx.usage.GetUsage(t) +} + +ghost +decreases +requires ctx.Props() +/** wrapper to get and check usage of a term for convenience */ +pure func (ctx LabelingContext) IsPkeKey(sk tm.Term) bool { + return (ctx.GetUsage(sk) != none[u.Usage]) && (get(ctx.GetUsage(sk))).IsPkeKey() +} + +ghost +decreases +requires ctx.Props() +/** wrapper to get and check usage of a term for convenience */ +pure func (ctx LabelingContext) IsSigningKey(sk tm.Term) bool { + return (ctx.GetUsage(sk) != none[u.Usage]) && (get(ctx.GetUsage(sk))).IsSigningKey() +} + +ghost +decreases +requires ctx.Props() +/** wrapper to get and check usage of a term for convenience */ +pure func (ctx LabelingContext) HasAeadKeyUsage(key tm.Term) bool { + return (ctx.GetUsage(key) != none[u.Usage]) && (get(ctx.GetUsage(key))).IsAeadKey() +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) IsMsg(t tr.TraceEntry, term tm.Term, l label.SecrecyLabel) bool { + return ctx.IsValid(t, term) && + ctx.CanFlow(t, ctx.GetLabel(term), l) +} + +ghost +decreases +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsMsg(t1, term, l) +ensures ctx.IsMsg(t2, term, l) +func (ctx LabelingContext) IsMsgMonotonic(t1, t2 tr.TraceEntry, term tm.Term, l label.SecrecyLabel) { + ctx.IsValidMonotonic(t1, t2, term) + ctx.CanFlowMonotonic(t1, t2, ctx.GetLabel(term), l) +} + +ghost +decreases +requires ctx.Props() +requires ctx.CanFlow(t, l1, l2) +requires ctx.IsMsg(t, term, l1) +ensures ctx.IsMsg(t, term, l2) +func (ctx LabelingContext) IsMsgTransitive(t tr.TraceEntry, term tm.Term, l1, l2 label.SecrecyLabel) { + ctx.CanFlowTransitive(t, ctx.GetLabel(term), l1, l2) +} + +ghost +decreases +requires ctx.Props() +requires term.IsTuple() +requires forall i int :: { tm.getTupleElem(term, i) } 0 <= i && i < term.GetTupleArity() ==> ctx.IsMsg(t, tm.getTupleElem(term, i), l) +ensures ctx.IsMsg(t, term, l) +/** lemma for tuples having same label */ +func (ctx LabelingContext) IsMsgTupleCreate(t tr.TraceEntry, term tm.Term, l label.SecrecyLabel) { + n := term.GetTupleArity() + subterms := getTupleSeq(term) + secondToLastT := subterms[n-2] + secondToLastL := ctx.GetLabel(secondToLastT) + lastT := subterms[n-1] + lastL := ctx.GetLabel(lastT) + ctx.CanFlowCreateMeetLhs(t, secondToLastL, lastL, l) + prevLabel := ctx.nestedMeet(term, subterms, n - 2) + + invariant 0 <= i && i <= n - 2 + invariant len(getTupleSeq(term)) == n + invariant prevLabel == ctx.nestedMeet(term, subterms, i) + invariant ctx.CanFlow(t, prevLabel, l) + decreases i + for i := n - 2; i >= 1; i-- { + curT := subterms[i-1] + curL := ctx.GetLabel(curT) + ctx.CanFlowCreateMeetLhs(t, curL, prevLabel, l) + prevLabel = label.Meet(curL, prevLabel) + } + + firstL := ctx.GetLabel(tm.getTupleElem(term, 0)) + ctx.CanFlowReflexive(t, l) + ctx.CanFlowCreateMeetLhs(t, firstL, l, l) +} + +ghost +decreases +requires ctx.Props() +requires term.IsTuple() +requires ctx.IsMsg(t, term, l) +ensures forall i int :: { tm.getTupleElem(term, i) } 0 <= i && i < term.GetTupleArity() ==> ctx.IsMsg(t, tm.getTupleElem(term, i), l) +/** lemma for tuples flowing to some label */ +func (ctx LabelingContext) IsMsgTupleResolve(t tr.TraceEntry, term tm.Term, l label.SecrecyLabel) { + n := term.GetTupleArity() + subterms := getTupleSeq(term) + + invariant 0 <= i && i <= n - 2 + invariant len(getTupleSeq(term)) == n + invariant ctx.CanFlow(t, ctx.nestedMeet(term, subterms, i), l) + invariant forall j int :: { tm.getTupleElem(term, j) } 0 <= j && j < i ==> ctx.IsMsg(t, tm.getTupleElem(term, j), l) + decreases n - i + for i := 0; i < n - 2; i++ { + curT := tm.getTupleElem(term, i) + curL := ctx.GetLabel(curT) + remL := ctx.nestedMeet(term, subterms, i + 1) + ctx.CanFlowResolveMeetLhs(t, curL, remL, l) + } + secondToLastT := tm.getTupleElem(term, n - 2) + secondToLastL := ctx.GetLabel(secondToLastT) + lastT := tm.getTupleElem(term, n - 1) + lastL := ctx.GetLabel(lastT) + ctx.CanFlowResolveMeetLhs(t, secondToLastL, lastL, l) +} + +ghost +decreases +requires ctx.CanFlow(t, l1, l3) +requires ctx.CanFlow(t, l2, l3) +ensures ctx.CanFlow(t, label.Meet(l1, l2), l3) +func (ctx LabelingContext) CanFlowCreateMeetLhs(t tr.TraceEntry, l1, l2, l3 label.SecrecyLabel) { + ctx.canFlowInternalCreateMeetLhs(t.getCorruptIds(), l1, l2, l3) +} + +ghost +decreases +requires ctx.canFlowInternal(corruptIds, l1, l3) +requires ctx.canFlowInternal(corruptIds, l2, l3) +ensures ctx.canFlowInternal(corruptIds, label.Meet(l1, l2), l3) +func (ctx LabelingContext) canFlowInternalCreateMeetLhs(corruptIds set[p.Id], l1, l2, l3 label.SecrecyLabel) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l1, l3) + ctx.canFlowInternalImpliesDYStar(corruptIds, l2, l3) + ctx.canFlowInternalCreateMeetLhs_DYStar(corruptIds, l1, l2, l3) + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, label.Meet(l1, l2), l3) +} + +ghost +decreases +requires ctx.canFlowInternal_DYStar(corruptIds, l1, l3) +requires ctx.canFlowInternal_DYStar(corruptIds, l2, l3) +ensures ctx.canFlowInternal_DYStar(corruptIds, label.Meet(l1, l2), l3) +func (ctx LabelingContext) canFlowInternalCreateMeetLhs_DYStar(corruptIds set[p.Id], l1, l2, l3 label.SecrecyLabel) { + // no body needed +} + +ghost +decreases +requires ctx.CanFlow(t, label.Meet(l1, l2), l3) +ensures ctx.CanFlow(t, l1, l3) +ensures ctx.CanFlow(t, l2, l3) +func (ctx LabelingContext) CanFlowResolveMeetLhs(t tr.TraceEntry, l1, l2, l3 label.SecrecyLabel) { + ctx.canFlowInternalResolveMeetLhs(t.getCorruptIds(), l1, l2, l3) +} + +ghost +decreases +requires ctx.canFlowInternal(corruptIds, label.Meet(l1, l2), l3) +ensures ctx.canFlowInternal(corruptIds, l1, l3) +ensures ctx.canFlowInternal(corruptIds, l2, l3) +func (ctx LabelingContext) canFlowInternalResolveMeetLhs(corruptIds set[p.Id], l1, l2, l3 label.SecrecyLabel) { + ctx.canFlowInternalImpliesDYStar(corruptIds, label.Meet(l1, l2), l3) + ctx.canFlowInternalResolveMeetLhs_DYStar(corruptIds, l1, l2, l3) + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l1, l3) + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l2, l3) +} + +ghost +decreases label.GetHeight(l3) +requires ctx.canFlowInternal_DYStar(corruptIds, label.Meet(l1, l2), l3) +ensures ctx.canFlowInternal_DYStar(corruptIds, l1, l3) +ensures ctx.canFlowInternal_DYStar(corruptIds, l2, l3) +func (ctx LabelingContext) canFlowInternalResolveMeetLhs_DYStar(corruptIds set[p.Id], l1, l2, l3 label.SecrecyLabel) { + if (l3.IsMeet() || l3.IsJoin()) { + if (ctx.canFlowInternal_DYStar(corruptIds, label.Meet(l1, l2), label.GetFirstLabel(l3))) { + ctx.canFlowInternalResolveMeetLhs_DYStar(corruptIds, l1, l2, label.GetFirstLabel(l3)) + } + if (ctx.canFlowInternal_DYStar(corruptIds, label.Meet(l1, l2), label.GetSecondLabel(l3))) { + ctx.canFlowInternalResolveMeetLhs_DYStar(corruptIds, l1, l2, label.GetSecondLabel(l3)) + } + } +} + +ghost +decreases +requires ctx.CanFlow(t, l1, l2) || ctx.CanFlow(t, l1, l3) +ensures ctx.CanFlow(t, l1, label.Meet(l2, l3)) +func (ctx LabelingContext) CanFlowCreateMeetRhs(t tr.TraceEntry, l1, l2, l3 label.SecrecyLabel) { + ctx.canFlowInternalCreateMeetRhs(t.getCorruptIds(), l1, l2, l3) +} + +ghost +decreases +requires ctx.canFlowInternal(corruptIds, l1, l2) || ctx.canFlowInternal(corruptIds, l1, l3) +ensures ctx.canFlowInternal(corruptIds, l1, label.Meet(l2, l3)) +func (ctx LabelingContext) canFlowInternalCreateMeetRhs(corruptIds set[p.Id], l1, l2, l3 label.SecrecyLabel) { + if ctx.canFlowInternal(corruptIds, l1, l2) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l1, l2) + } + if ctx.canFlowInternal(corruptIds, l1, l3) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l1, l3) + } + ctx.canFlowInternalCreateMeetRhs_DYStar(corruptIds, l1, l2, l3) + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l1, label.Meet(l2, l3)) +} + +ghost +decreases +requires ctx.canFlowInternal_DYStar(corruptIds, l1, l2) || ctx.canFlowInternal_DYStar(corruptIds, l1, l3) +ensures ctx.canFlowInternal_DYStar(corruptIds, l1, label.Meet(l2, l3)) +func (ctx LabelingContext) canFlowInternalCreateMeetRhs_DYStar(corruptIds set[p.Id], l1, l2, l3 label.SecrecyLabel) { + // no body needed +} + +ghost +decreases +requires ctx.CanFlow(t, l1, l2) +requires ctx.CanFlow(t, l3, l4) +ensures ctx.CanFlow(t, label.Meet(l1, l3), label.Meet(l2, l4)) +func (ctx LabelingContext) CanFlowCreateMeetBoth(t tr.TraceEntry, l1, l2, l3, l4 label.SecrecyLabel) { + ctx.canFlowInternalCreateMeetBoth(t.getCorruptIds(), l1, l2, l3, l4) +} + +ghost +decreases +requires ctx.canFlowInternal(corruptIds, l1, l2) +requires ctx.canFlowInternal(corruptIds, l3, l4) +ensures ctx.canFlowInternal(corruptIds, label.Meet(l1, l3), label.Meet(l2, l4)) +func (ctx LabelingContext) canFlowInternalCreateMeetBoth(corruptIds set[p.Id], l1, l2, l3, l4 label.SecrecyLabel) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l1, l2) + ctx.canFlowInternalImpliesDYStar(corruptIds, l3, l4) + ctx.canFlowInternalCreateMeetBoth_DYStar(corruptIds, l1, l2, l3, l4) + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, label.Meet(l1, l3), label.Meet(l2, l4)) +} + +ghost +decreases +requires ctx.canFlowInternal_DYStar(corruptIds, l1, l2) +requires ctx.canFlowInternal_DYStar(corruptIds, l3, l4) +ensures ctx.canFlowInternal_DYStar(corruptIds, label.Meet(l1, l3), label.Meet(l2, l4)) +func (ctx LabelingContext) canFlowInternalCreateMeetBoth_DYStar(corruptIds set[p.Id], l1, l2, l3, l4 label.SecrecyLabel) { + ctx.canFlowInternalCreateMeetRhs_DYStar(corruptIds, l1, l2, l4) + ctx.canFlowInternalCreateMeetRhs_DYStar(corruptIds, l3, l2, l4) +} + +ghost +decreases +requires ctx.CanFlow(t, l1, l2) +requires ctx.CanFlow(t, l3, l4) +ensures ctx.CanFlow(t, label.Join(l1, l3), label.Join(l2, l4)) +func (ctx LabelingContext) CanFlowCreateJoinBoth(t tr.TraceEntry, l1, l2, l3, l4 label.SecrecyLabel) { + ctx.canFlowInternalCreateJoinBoth(t.getCorruptIds(), l1, l2, l3, l4) +} + +ghost +decreases +requires ctx.canFlowInternal(corruptIds, l1, l2) +requires ctx.canFlowInternal(corruptIds, l3, l4) +ensures ctx.canFlowInternal(corruptIds, label.Join(l1, l3), label.Join(l2, l4)) +func (ctx LabelingContext) canFlowInternalCreateJoinBoth(corruptIds IdSet, l1, l2, l3, l4 label.SecrecyLabel) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l1, l2) + ctx.canFlowInternalImpliesDYStar(corruptIds, l3, l4) + ctx.canFlowInternalCreateJoinBoth_DYStar(corruptIds, l1, l2, l3, l4) + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, label.Join(l1, l3), label.Join(l2, l4)) +} + +ghost +decreases +requires ctx.canFlowInternal_DYStar(corruptIds, l1, l2) +requires ctx.canFlowInternal_DYStar(corruptIds, l3, l4) +ensures ctx.canFlowInternal_DYStar(corruptIds, label.Join(l1, l3), label.Join(l2, l4)) +func (ctx LabelingContext) canFlowInternalCreateJoinBoth_DYStar(corruptIds IdSet, l1, l2, l3, l4 label.SecrecyLabel) { + ctx.canFlowInternalCreateJoinLhs_DYStar(corruptIds, l1, l3, l2) + ctx.canFlowInternalCreateJoinLhs_DYStar(corruptIds, l1, l3, l4) +} + +ghost +decreases +requires ctx.CanFlow(t, l1, label.Meet(l2, l3)) +requires ctx.CanFlow(t, l2, l3) +ensures ctx.CanFlow(t, l1, l3) +func (ctx LabelingContext) CanFlowResolveMeetRhs(t tr.TraceEntry, l1, l2, l3 label.SecrecyLabel) { + ctx.canFlowInternalResolveMeetRhs(t.getCorruptIds(), l1, l2, l3) +} + +ghost +decreases +requires ctx.canFlowInternal(corruptIds, l1, label.Meet(l2, l3)) +requires ctx.canFlowInternal(corruptIds, l2, l3) +ensures ctx.canFlowInternal(corruptIds, l1, l3) +func (ctx LabelingContext) canFlowInternalResolveMeetRhs(corruptIds set[p.Id], l1, l2, l3 label.SecrecyLabel) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l1, label.Meet(l2, l3)) + ctx.canFlowInternalImpliesDYStar(corruptIds, l2, l3) + ctx.canFlowInternalResolveMeetRhs_DYStar(corruptIds, l1, l2, l3) + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l1, l3) +} + +ghost +decreases label.GetHeight(l1) +requires ctx.canFlowInternal_DYStar(corruptIds, l1, label.Meet(l2, l3)) +requires ctx.canFlowInternal_DYStar(corruptIds, l2, l3) +ensures ctx.canFlowInternal_DYStar(corruptIds, l1, l3) +func (ctx LabelingContext) canFlowInternalResolveMeetRhs_DYStar(corruptIds set[p.Id], l1, l2, l3 label.SecrecyLabel) { + if ctx.canFlowInternal_DYStar(corruptIds, l1, l2) { + ctx.canFlowInternalTransitive_DYStar(corruptIds, l1, l2, l3) + } + if l1.IsMeet() { + if !ctx.canFlowInternal_DYStar(corruptIds, l1, l3) { + ctx.canFlowInternalResolveMeetRhs_DYStar(corruptIds, label.GetFirstLabel(l1), l2, l3) + ctx.canFlowInternalResolveMeetRhs_DYStar(corruptIds, label.GetSecondLabel(l1), l2, l3) + } + } else if l1.IsJoin() { + if ctx.canFlowInternal_DYStar(corruptIds, label.GetFirstLabel(l1), label.Meet(l2, l3)) { + ctx.canFlowInternalResolveMeetRhs_DYStar(corruptIds, label.GetFirstLabel(l1), l2, l3) + } else if ctx.canFlowInternal_DYStar(corruptIds, label.GetSecondLabel(l1), label.Meet(l2, l3)) { + ctx.canFlowInternalResolveMeetRhs_DYStar(corruptIds, label.GetSecondLabel(l1), l2, l3) + } + } +} + +ghost +decreases +requires ctx.CanFlow(t, l1, l3) || ctx.CanFlow(t, l2, l3) +ensures ctx.CanFlow(t, label.Join(l1, l2), l3) +func (ctx LabelingContext) CanFlowCreateJoinLhs(t tr.TraceEntry, l1, l2, l3 label.SecrecyLabel) { + ctx.canFlowInternalCreateJoinLhs(t.getCorruptIds(), l1, l2, l3) +} + +ghost +decreases +requires ctx.canFlowInternal(corruptIds, l1, l3) || ctx.canFlowInternal(corruptIds, l2, l3) +ensures ctx.canFlowInternal(corruptIds, label.Join(l1, l2), l3) +func (ctx LabelingContext) canFlowInternalCreateJoinLhs(corruptIds IdSet, l1, l2, l3 label.SecrecyLabel) { + if ctx.canFlowInternal(corruptIds, l1, l3) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l1, l3) + } else { + ctx.canFlowInternalImpliesDYStar(corruptIds, l2, l3) + } + ctx.canFlowInternalCreateJoinLhs_DYStar(corruptIds, l1, l2, l3) + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, label.Join(l1, l2), l3) +} + +ghost +decreases +requires ctx.canFlowInternal_DYStar(corruptIds, l1, l3) || ctx.canFlowInternal_DYStar(corruptIds, l2, l3) +ensures ctx.canFlowInternal_DYStar(corruptIds, label.Join(l1, l2), l3) +func (ctx LabelingContext) canFlowInternalCreateJoinLhs_DYStar(corruptIds set[p.Id], l1, l2, l3 label.SecrecyLabel) { + // no body needed +} + +ghost +decreases label.GetHeight(l2) +requires ctx.canFlowInternal_DYStar(corruptIds, label.Join(l1, l1), l2) +ensures ctx.canFlowInternal_DYStar(corruptIds, l1, l2) +// note that it does not hold that ctx.canFlowInternal_DYStar(corruptIds, label.Join(l1, l2), l3) ==> +// (ctx.canFlowInternal_DYStar(corruptIds, l1, l3) || +// ctx.canFlowInternal_DYStar(corruptIds, l2, l3)) +// the lhs is weaker because it permits that l1 flows to l31 and l2 to l32 (if l3 == Meet(l31, l32)) +func (ctx LabelingContext) canFlowInternalResolveJoinLhs_DYStar(corruptIds set[p.Id], l1, l2 label.SecrecyLabel) { + if (l2.IsMeet()) { + if (ctx.canFlowInternal_DYStar(corruptIds, label.Join(l1, l1), label.GetFirstLabel(l2))) { + ctx.canFlowInternalResolveJoinLhs_DYStar(corruptIds, l1, label.GetFirstLabel(l2)) + } else if (ctx.canFlowInternal_DYStar(corruptIds, label.Join(l1, l1), label.GetSecondLabel(l2))) { + ctx.canFlowInternalResolveJoinLhs_DYStar(corruptIds, l1, label.GetSecondLabel(l2)) + } + } else if (l2.IsJoin()) { + if (!ctx.canFlowInternal_DYStar(corruptIds, l1, l2)) { + ctx.canFlowInternalResolveJoinLhs_DYStar(corruptIds, l1, label.GetFirstLabel(l2)) + ctx.canFlowInternalResolveJoinLhs_DYStar(corruptIds, l1, label.GetSecondLabel(l2)) + } + } +} + +ghost +decreases +requires ctx.canFlowInternal_DYStar(corruptIds, l1, l2) +requires ctx.canFlowInternal_DYStar(corruptIds, l1, l3) +ensures ctx.canFlowInternal_DYStar(corruptIds, l1, label.Join(l2, l3)) +func (ctx LabelingContext) canFlowInternalCreateJoinRhs_DYStar(corruptIds set[p.Id], l1, l2, l3 label.SecrecyLabel) { + // no body needed +} + +ghost +decreases +requires ctx.CanFlow(t, l1, label.Join(l2, l3)) +ensures ctx.CanFlow(t, l1, l2) +ensures ctx.CanFlow(t, l1, l3) +func (ctx LabelingContext) CanFlowResolveJoinRhs(t tr.TraceEntry, l1, l2, l3 label.SecrecyLabel) { + ctx.canFlowInternalResolveJoinRhs(t.getCorruptIds(), l1, l2, l3) +} + +ghost +decreases +requires ctx.canFlowInternal(corruptIds, l1, label.Join(l2, l3)) +ensures ctx.canFlowInternal(corruptIds, l1, l2) +ensures ctx.canFlowInternal(corruptIds, l1, l3) +func (ctx LabelingContext) canFlowInternalResolveJoinRhs(corruptIds set[p.Id], l1, l2, l3 label.SecrecyLabel) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l1, label.Join(l2, l3)) + ctx.canFlowInternalResolveJoinRhs_DYStar(corruptIds, l1, l2, l3) + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l1, l2) + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l1, l3) +} + +ghost +decreases label.GetHeight(l1) +requires ctx.canFlowInternal_DYStar(corruptIds, l1, label.Join(l2, l3)) +ensures ctx.canFlowInternal_DYStar(corruptIds, l1, l2) +ensures ctx.canFlowInternal_DYStar(corruptIds, l1, l3) +func (ctx LabelingContext) canFlowInternalResolveJoinRhs_DYStar(corruptIds set[p.Id], l1, l2, l3 label.SecrecyLabel) { + if (l1.IsMeet() || l1.IsJoin()) { + if (ctx.canFlowInternal_DYStar(corruptIds, label.GetFirstLabel(l1), label.Join(l2, l3))) { + ctx.canFlowInternalResolveJoinRhs_DYStar(corruptIds, label.GetFirstLabel(l1), l2, l3) + } + if (ctx.canFlowInternal_DYStar(corruptIds, label.GetSecondLabel(l1), label.Join(l2, l3))) { + ctx.canFlowInternalResolveJoinRhs_DYStar(corruptIds, label.GetSecondLabel(l1), l2, l3) + } + } +} + +ghost +decreases +requires ctx.CanFlow(t, l1, label.Meet(l2, l3)) +requires l2FlowsToPublic ==> ctx.CanFlow(t, l2, label.Public()) +requires !l2FlowsToPublic ==> ctx.CanFlow(t, l3, label.Public()) +ensures l2FlowsToPublic ==> ctx.CanFlow(t, l1, l3) +ensures !l2FlowsToPublic ==> ctx.CanFlow(t, l1, l2) +func (ctx LabelingContext) CanFlowResolveMeetPublicRhs(t tr.TraceEntry, l1, l2, l3 label.SecrecyLabel, l2FlowsToPublic bool) { + ctx.canFlowInternalResolveMeetPublicRhs(t.getCorruptIds(), l1, l2, l3, l2FlowsToPublic) +} + +ghost +decreases +requires ctx.canFlowInternal(corruptIds, l1, label.Meet(l2, l3)) +requires l2FlowsToPublic ==> ctx.canFlowInternal(corruptIds, l2, label.Public()) +requires !l2FlowsToPublic ==> ctx.canFlowInternal(corruptIds, l3, label.Public()) +ensures l2FlowsToPublic ==> ctx.canFlowInternal(corruptIds, l1, l3) +ensures !l2FlowsToPublic ==> ctx.canFlowInternal(corruptIds, l1, l2) +func (ctx LabelingContext) canFlowInternalResolveMeetPublicRhs(corruptIds set[p.Id], l1, l2, l3 label.SecrecyLabel, l2FlowsToPublic bool) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l1, label.Meet(l2, l3)) + if l2FlowsToPublic { + ctx.canFlowInternalImpliesDYStar(corruptIds, l2, label.Public()) + } else { + ctx.canFlowInternalImpliesDYStar(corruptIds, l3, label.Public()) + } + ctx.canFlowInternalResolveMeetPublicRhs_DYStar(corruptIds, l1, l2, l3, l2FlowsToPublic) + if l2FlowsToPublic { + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l1, l3) + } else { + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l1, l2) + } +} + +ghost +decreases label.GetHeight(l1) +requires ctx.canFlowInternal_DYStar(corruptIds, l1, label.Meet(l2, l3)) +requires l2FlowsToPublic ==> ctx.canFlowInternal_DYStar(corruptIds, l2, label.Public()) +requires !l2FlowsToPublic ==> ctx.canFlowInternal_DYStar(corruptIds, l3, label.Public()) +ensures l2FlowsToPublic ==> ctx.canFlowInternal_DYStar(corruptIds, l1, l3) +ensures !l2FlowsToPublic ==> ctx.canFlowInternal_DYStar(corruptIds, l1, l2) +func (ctx LabelingContext) canFlowInternalResolveMeetPublicRhs_DYStar(corruptIds set[p.Id], l1, l2, l3 label.SecrecyLabel, l2FlowsToPublic bool) { + if (ctx.canFlowInternal_DYStar(corruptIds, l1, label.Public())) { + ctx.flowsToPublicCanFlowInternal_DYStar(corruptIds, l1, l2) + ctx.flowsToPublicCanFlowInternal_DYStar(corruptIds, l1, l3) + } + if ctx.canFlowInternal_DYStar(corruptIds, l1, l2) { + ctx.canFlowInternalTransitive_DYStar(corruptIds, l1, l2, label.Public()) + } + if ctx.canFlowInternal_DYStar(corruptIds, l1, l3) { + ctx.canFlowInternalTransitive_DYStar(corruptIds, l1, l3, label.Public()) + } + + if (l1.IsMeet()) { + if (!ctx.canFlowInternal_DYStar(corruptIds, l1, l2) && !ctx.canFlowInternal_DYStar(corruptIds, l1, l3)) { + ctx.canFlowInternalResolveMeetPublicRhs_DYStar(corruptIds, label.GetFirstLabel(l1), l2, l3, l2FlowsToPublic) + ctx.canFlowInternalResolveMeetPublicRhs_DYStar(corruptIds, label.GetSecondLabel(l1), l2, l3, l2FlowsToPublic) + } + } else if (l1.IsJoin()) { + if (ctx.canFlowInternal_DYStar(corruptIds, label.GetFirstLabel(l1), label.Meet(l2, l3))) { + ctx.canFlowInternalResolveMeetPublicRhs_DYStar(corruptIds, label.GetFirstLabel(l1), l2, l3, l2FlowsToPublic) + } else if (ctx.canFlowInternal_DYStar(corruptIds, label.GetSecondLabel(l1), label.Meet(l2, l3))) { + ctx.canFlowInternalResolveMeetPublicRhs_DYStar(corruptIds, label.GetSecondLabel(l1), l2, l3, l2FlowsToPublic) + } + } +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) IsPublishable(t tr.TraceEntry, term tm.Term) bool { + return ctx.IsMsg(t, term, label.Public()) +} + +ghost +decreases +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsPublishable(t1, term) +ensures ctx.IsPublishable(t2, term) +func (ctx LabelingContext) IsPublishableMonotonic(t1, t2 tr.TraceEntry, term tm.Term) { + ctx.IsMsgMonotonic(t1, t2, term, label.Public()) +} + +ghost +decreases +pure func (ctx LabelingContext) CanFlow(t tr.TraceEntry, l1, l2 label.SecrecyLabel) bool { + return ctx.canFlowInternal(t.getCorruptIds(), l1, l2) +} + +ghost +decreases label.GetHeight(l1), label.GetHeight(l2) +/** this is the canFlowInternal function that we use throughout */ +pure func (ctx LabelingContext) canFlowInternal(corruptIds set[p.Id], l1, l2 label.SecrecyLabel) bool { + // public can flow to anything + return l1.IsPublic() ? true : + ////////// begin special cases ////////// + // begin special case 1: meet or join of two identical labels is identical to one of them (l1 case) + ((l1.IsMeet() || l1.IsJoin()) && label.GetFirstLabel(l1) == label.GetSecondLabel(l1)) ? ctx.canFlowInternal(corruptIds, label.GetFirstLabel(l1), l2) : + // end special case 1 + // begin special case 2: meet or join of two identical labels is identical to one of them (l2 case) + ((l2.IsMeet() || l2.IsJoin()) && label.GetFirstLabel(l2) == label.GetSecondLabel(l2)) ? ctx.canFlowInternal(corruptIds, l1, label.GetFirstLabel(l2)) : + // end special case 2 + // begin special case 3: meet of two labels, one of which being public (l1 case) + (l1.IsMeet() && label.GetFirstLabel(l1).IsPublic()) ? ctx.canFlowInternal(corruptIds, label.GetSecondLabel(l1), l2) : + (l1.IsMeet() && label.GetSecondLabel(l1).IsPublic()) ? ctx.canFlowInternal(corruptIds, label.GetFirstLabel(l1), l2) : + // end special case 3 + // begin special case 4: meet of two labels, one of which being public (l2 case) + (l2.IsMeet() && label.GetFirstLabel(l2).IsPublic()) ? ctx.canFlowInternal(corruptIds, l1, label.GetSecondLabel(l2)) : + (l2.IsMeet() && label.GetSecondLabel(l2).IsPublic()) ? ctx.canFlowInternal(corruptIds, l1, label.GetFirstLabel(l2)) : + // end special case 4 + ////////// end special cases ////////// + // label with a corrupted reader flows to public + (l1.IsReaders() && l2.IsPublic()) ? tr.containsCorruptId(corruptIds, label.GetReaders(l1)) : + // l1 is superset of l2 or some ID in l1 is corrupt + (l1.IsReaders() && l2.IsReaders()) ? (includesIds(label.GetReaders(l1), label.GetReaders(l2)) || tr.containsCorruptId(corruptIds, label.GetReaders(l1))) : + // l1 flows to l2 == meet(l21, l22) if l1 flows to l21 OR l22 because meet(l21, l22) is stricter than l21 or l22 + (l1.IsReaders() && l2.IsMeet()) ? (ctx.canFlowInternal(corruptIds, l1, label.GetFirstLabel(l2)) || ctx.canFlowInternal(corruptIds, l1, label.GetSecondLabel(l2))) : + // l1 flows to l2 == join(l21, l22) if l1 flows to l21 AND l22 + (l1.IsReaders() && l2.IsJoin()) ? (ctx.canFlowInternal(corruptIds, l1, label.GetFirstLabel(l2)) && ctx.canFlowInternal(corruptIds, l1, label.GetSecondLabel(l2))) : + (l1.IsJoin() && (l2.IsPublic() || l2.IsReaders())) ? (ctx.canFlowInternal(corruptIds, label.GetFirstLabel(l1), l2) || ctx.canFlowInternal(corruptIds, label.GetSecondLabel(l1), l2)) : + (l1.IsMeet() && (l2.IsPublic() || l2.IsReaders())) ? (ctx.canFlowInternal(corruptIds, label.GetFirstLabel(l1), l2) && ctx.canFlowInternal(corruptIds, label.GetSecondLabel(l1), l2)) : + (l1.IsJoin() && l2.IsMeet()) ? ((ctx.canFlowInternal(corruptIds, l1, label.GetFirstLabel(l2)) || ctx.canFlowInternal(corruptIds, l1, label.GetSecondLabel(l2))) || (ctx.canFlowInternal(corruptIds, label.GetFirstLabel(l1), l2) || ctx.canFlowInternal(corruptIds, label.GetSecondLabel(l1), l2))) : + (l1.IsMeet() && l2.IsMeet()) ? ((ctx.canFlowInternal(corruptIds, l1, label.GetFirstLabel(l2)) || ctx.canFlowInternal(corruptIds, l1, label.GetSecondLabel(l2))) || (ctx.canFlowInternal(corruptIds, label.GetFirstLabel(l1), l2) && ctx.canFlowInternal(corruptIds, label.GetSecondLabel(l1), l2))) : + (l1.IsMeet() && l2.IsJoin()) ? ((ctx.canFlowInternal(corruptIds, l1, label.GetFirstLabel(l2)) && ctx.canFlowInternal(corruptIds, l1, label.GetSecondLabel(l2))) || (ctx.canFlowInternal(corruptIds, label.GetFirstLabel(l1), l2) && ctx.canFlowInternal(corruptIds, label.GetSecondLabel(l1), l2))) : + (l1.IsJoin() && l2.IsJoin()) ? ((ctx.canFlowInternal(corruptIds, l1, label.GetFirstLabel(l2)) && ctx.canFlowInternal(corruptIds, l1, label.GetSecondLabel(l2))) || (ctx.canFlowInternal(corruptIds, label.GetFirstLabel(l1), l2) || ctx.canFlowInternal(corruptIds, label.GetSecondLabel(l1), l2))) : + false + // rule of thumb: + // l1 being a join or l2 being a meet results in disjunctions + // l1 being a meet or l2 being a join results in conjunctions +} + +ghost +/** + * this is the orginal `can_flow` function used by DY*. + * we show below that `canFlowInternal` and `canFlowInternal_DYStar` are equivalent. + */ +pure func (ctx LabelingContext) canFlowInternal_DYStar(corruptIds IdSet, l1, l2 label.SecrecyLabel) bool { + // public can flow to anything + return (l1.IsPublic() ==> true) && + // label with a corrupted reader flows to public + (l1.IsReaders() && l2.IsPublic() ==> tr.containsCorruptId(corruptIds, label.GetReaders(l1))) && + // l1 is superset of l2 or some ID in l1 is corrupt + (l1.IsReaders() && l2.IsReaders() ==> (includesIds(label.GetReaders(l1), label.GetReaders(l2)) || tr.containsCorruptId(corruptIds, label.GetReaders(l1)))) && + // l1 flows to l2 == meet(l21, l22) if l1 flows to l21 OR l22 because meet(l21, l22) is stricter than l21 or l22 + (l1.IsReaders() && l2.IsMeet() ==> ctx.canFlowInternal_DYStar(corruptIds, l1, label.GetFirstLabel(l2)) || ctx.canFlowInternal_DYStar(corruptIds, l1, label.GetSecondLabel(l2))) && + // l1 flows to l2 == join(l21, l22) if l1 flows to l21 AND l22 + (l1.IsReaders() && l2.IsJoin() ==> ctx.canFlowInternal_DYStar(corruptIds, l1, label.GetFirstLabel(l2)) && ctx.canFlowInternal_DYStar(corruptIds, l1, label.GetSecondLabel(l2))) && + (l1.IsJoin() && (l2.IsPublic() || l2.IsReaders()) ==> ctx.canFlowInternal_DYStar(corruptIds, label.GetFirstLabel(l1), l2) || ctx.canFlowInternal_DYStar(corruptIds, label.GetSecondLabel(l1), l2)) && + (l1.IsMeet() && (l2.IsPublic() || l2.IsReaders()) ==> ctx.canFlowInternal_DYStar(corruptIds, label.GetFirstLabel(l1), l2) && ctx.canFlowInternal_DYStar(corruptIds, label.GetSecondLabel(l1), l2)) && + (l1.IsJoin() && l2.IsMeet() ==> (ctx.canFlowInternal_DYStar(corruptIds, l1, label.GetFirstLabel(l2)) || ctx.canFlowInternal_DYStar(corruptIds, l1, label.GetSecondLabel(l2))) || (ctx.canFlowInternal_DYStar(corruptIds, label.GetFirstLabel(l1), l2) || ctx.canFlowInternal_DYStar(corruptIds, label.GetSecondLabel(l1), l2))) && + (l1.IsMeet() && l2.IsMeet() ==> (ctx.canFlowInternal_DYStar(corruptIds, l1, label.GetFirstLabel(l2)) || ctx.canFlowInternal_DYStar(corruptIds, l1, label.GetSecondLabel(l2))) || (ctx.canFlowInternal_DYStar(corruptIds, label.GetFirstLabel(l1), l2) && ctx.canFlowInternal_DYStar(corruptIds, label.GetSecondLabel(l1), l2))) && + (l1.IsMeet() && l2.IsJoin() ==> (ctx.canFlowInternal_DYStar(corruptIds, l1, label.GetFirstLabel(l2)) && ctx.canFlowInternal_DYStar(corruptIds, l1, label.GetSecondLabel(l2))) || (ctx.canFlowInternal_DYStar(corruptIds, label.GetFirstLabel(l1), l2) && ctx.canFlowInternal_DYStar(corruptIds, label.GetSecondLabel(l1), l2))) && + (l1.IsJoin() && l2.IsJoin() ==> (ctx.canFlowInternal_DYStar(corruptIds, l1, label.GetFirstLabel(l2)) && ctx.canFlowInternal_DYStar(corruptIds, l1, label.GetSecondLabel(l2))) || (ctx.canFlowInternal_DYStar(corruptIds, label.GetFirstLabel(l1), l2) || ctx.canFlowInternal_DYStar(corruptIds, label.GetSecondLabel(l1), l2))) + // rule of thumb: + // l1 being a join or l2 being a meet results in disjunctions + // l1 being a meet or l2 being a join results in conjunctions +} + +ghost +decreases label.GetHeight(l1), label.GetHeight(l2) +requires ctx.canFlowInternal(corruptIds, l1, l2) +ensures ctx.canFlowInternal_DYStar(corruptIds, l1, l2) +func (ctx LabelingContext) canFlowInternalImpliesDYStar(corruptIds set[p.Id], l1, l2 label.SecrecyLabel) { + if (l1.IsMeet() || l1.IsJoin()) && label.GetFirstLabel(l1) == label.GetSecondLabel(l1) { + // special case 1 + l11 := label.GetFirstLabel(l1) + ctx.canFlowInternalImpliesDYStar(corruptIds, l11, l2) + if l1.IsMeet() { + ctx.canFlowInternalCreateMeetLhs_DYStar(corruptIds, l11, l11, l2) + } else { + ctx.canFlowInternalCreateJoinLhs_DYStar(corruptIds, l11, l11, l2) + } + } else if (l2.IsMeet() || l2.IsJoin()) && label.GetFirstLabel(l2) == label.GetSecondLabel(l2) { + // special case 2 + l21 := label.GetFirstLabel(l2) + ctx.canFlowInternalImpliesDYStar(corruptIds, l1, l21) + if l1.IsMeet() { + ctx.canFlowInternalCreateMeetRhs_DYStar(corruptIds, l1, l21, l21) + } else { + ctx.canFlowInternalCreateJoinRhs_DYStar(corruptIds, l1, l21, l21) + } + } else if l1.IsMeet() && (label.GetFirstLabel(l1).IsPublic() || label.GetSecondLabel(l1).IsPublic()) { + // special case 3 + l11 := label.GetFirstLabel(l1) + l12 := label.GetSecondLabel(l1) + ctx.canFlowInternalImpliesDYStar(corruptIds, l11, l2) + ctx.canFlowInternalImpliesDYStar(corruptIds, l12, l2) + ctx.canFlowInternalCreateMeetLhs_DYStar(corruptIds, l11, l12, l2) + } else if l2.IsMeet() && (label.GetFirstLabel(l2).IsPublic() || label.GetSecondLabel(l2).IsPublic()) { + // special case 4 + l21 := label.GetFirstLabel(l2) + l22 := label.GetSecondLabel(l2) + if ctx.canFlowInternal(corruptIds, l1, l21) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l1, l21) + } + if ctx.canFlowInternal(corruptIds, l1, l22) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l1, l22) + } + ctx.canFlowInternalCreateMeetRhs_DYStar(corruptIds, l1, l21, l22) + } else { + if l1.IsMeet() || l1.IsJoin() { + l11 := label.GetFirstLabel(l1) + l12 := label.GetSecondLabel(l1) + if ctx.canFlowInternal(corruptIds, l11, l2) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l11, l2) + } + if ctx.canFlowInternal(corruptIds, l12, l2) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l12, l2) + } + } + if l2.IsMeet() || l2.IsJoin() { + l21 := label.GetFirstLabel(l2) + l22 := label.GetSecondLabel(l2) + if ctx.canFlowInternal(corruptIds, l1, l21) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l1, l21) + } + if ctx.canFlowInternal(corruptIds, l1, l22) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l1, l22) + } + } + } +} + +ghost +decreases label.GetHeight(l1), label.GetHeight(l2) +requires ctx.canFlowInternal_DYStar(corruptIds, l1, l2) +ensures ctx.canFlowInternal(corruptIds, l1, l2) +func (ctx LabelingContext) canFlowInternalIsImpliesByDYStar(corruptIds set[p.Id], l1, l2 label.SecrecyLabel) { + if (l1.IsMeet() || l1.IsJoin()) && label.GetFirstLabel(l1) == label.GetSecondLabel(l1) { + // special case 1 + l11 := label.GetFirstLabel(l1) + if l1.IsMeet() { + ctx.canFlowInternalResolveMeetLhs_DYStar(corruptIds, l11, l11, l2) + } else { + ctx.canFlowInternalResolveJoinLhs_DYStar(corruptIds, l11, l2) + } + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l11, l2) + } else if (l2.IsMeet() || l2.IsJoin()) && label.GetFirstLabel(l2) == label.GetSecondLabel(l2) { + // special case 2 + l21 := label.GetFirstLabel(l2) + if l2.IsMeet() { + ctx.canFlowInternalReflexive_DYStar(corruptIds, l21) + ctx.canFlowInternalResolveMeetRhs_DYStar(corruptIds, l1, l21, l21) + } else { + ctx.canFlowInternalResolveJoinRhs_DYStar(corruptIds, l1, l21, l21) + } + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l1, l21) + } else if l1.IsMeet() && (label.GetFirstLabel(l1).IsPublic() || label.GetSecondLabel(l1).IsPublic()) { + // special case 3 + l11 := label.GetFirstLabel(l1) + l12 := label.GetSecondLabel(l1) + ctx.canFlowInternalResolveMeetLhs_DYStar(corruptIds, l11, l12, l2) + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l12, l2) + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l11, l2) + } else if l2.IsMeet() && (label.GetFirstLabel(l2).IsPublic() || label.GetSecondLabel(l2).IsPublic()) { + // special case 4 + l21 := label.GetFirstLabel(l2) + l22 := label.GetSecondLabel(l2) + ctx.canFlowInternalResolveMeetPublicRhs_DYStar(corruptIds, l1, l21, l22, l21.IsPublic()) + if l21.IsPublic() { + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l1, l22) + } else { + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l1, l21) + } + } else { + if l1.IsMeet() || l1.IsJoin() { + l11 := label.GetFirstLabel(l1) + l12 := label.GetSecondLabel(l1) + if ctx.canFlowInternal_DYStar(corruptIds, l11, l2) { + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l11, l2) + } + if ctx.canFlowInternal_DYStar(corruptIds, l12, l2) { + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l12, l2) + } + } + if l2.IsMeet() || l2.IsJoin() { + l21 := label.GetFirstLabel(l2) + l22 := label.GetSecondLabel(l2) + if ctx.canFlowInternal_DYStar(corruptIds, l1, l21) { + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l1, l21) + } + if ctx.canFlowInternal_DYStar(corruptIds, l1, l22) { + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l1, l22) + } + } + } +} + +ghost +decreases +ensures ctx.CanFlow(t, l, l) +func (ctx LabelingContext) CanFlowReflexive(t tr.TraceEntry, l label.SecrecyLabel) { + corruptIds := t.getCorruptIds() + ctx.canFlowInternalReflexive(corruptIds, l) +} + +ghost +decreases +ensures ctx.canFlowInternal(corruptIds, l, l) +func (ctx LabelingContext) canFlowInternalReflexive(corruptIds IdSet, l label.SecrecyLabel) { + ctx.canFlowInternalReflexive_DYStar(corruptIds, l) + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l, l) +} + +ghost +decreases label.GetHeight(l) +ensures ctx.canFlowInternal_DYStar(corruptIds, l, l) +func (ctx LabelingContext) canFlowInternalReflexive_DYStar(corruptIds IdSet, l label.SecrecyLabel) { + if l.IsReaders() { + ctx.includesIdsReflexive(label.GetReaders(l)) + } else if (l.IsJoin()) { + l1 := label.GetFirstLabel(l) + l2 := label.GetSecondLabel(l) + ctx.canFlowInternalReflexive_DYStar(corruptIds, l1) + ctx.canFlowInternalReflexive_DYStar(corruptIds, l2) + // the following assert stmts are needed + assert ctx.canFlowInternal_DYStar(corruptIds, l, l1) + assert ctx.canFlowInternal_DYStar(corruptIds, l, l2) + } else if (l.IsMeet()) { + l1 := label.GetFirstLabel(l) + l2 := label.GetSecondLabel(l) + ctx.canFlowInternalReflexive_DYStar(corruptIds, l1) + ctx.canFlowInternalReflexive_DYStar(corruptIds, l2) + // the following assert stmts are needed + assert ctx.canFlowInternal_DYStar(corruptIds, l1, l) + assert ctx.canFlowInternal_DYStar(corruptIds, l2, l) + } +} + +ghost +decreases +requires ctx.CanFlow(t, l1, l2) +requires ctx.CanFlow(t, l2, l3) +ensures ctx.CanFlow(t, l1, l3) +func (ctx LabelingContext) CanFlowTransitive(t tr.TraceEntry, l1, l2, l3 label.SecrecyLabel) { + corruptIds := t.getCorruptIds() + ctx.canFlowInternalTransitive(corruptIds, l1, l2, l3) +} + +ghost +decreases +ensures (ctx.canFlowInternal(corruptIds, l1, l2) && ctx.canFlowInternal(corruptIds, l2, l3)) ==> ctx.canFlowInternal(corruptIds, l1, l3) +func (ctx LabelingContext) canFlowInternalTransitive(corruptIds IdSet, l1, l2, l3 label.SecrecyLabel) { + if ctx.canFlowInternal(corruptIds, l1, l2) && ctx.canFlowInternal(corruptIds, l2, l3) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l1, l2) + ctx.canFlowInternalImpliesDYStar(corruptIds, l2, l3) + ctx.canFlowInternalTransitive_DYStar(corruptIds, l1, l2, l3) + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l1, l3) + } +} + +ghost +decreases label.GetHeight(l1), label.GetHeight(l2), label.GetHeight(l3) +ensures (ctx.canFlowInternal_DYStar(corruptIds, l1, l2) && ctx.canFlowInternal_DYStar(corruptIds, l2, l3)) ==> ctx.canFlowInternal_DYStar(corruptIds, l1, l3) +func (ctx LabelingContext) canFlowInternalTransitive_DYStar(corruptIds IdSet, l1, l2, l3 label.SecrecyLabel) { + if (l1.IsPublic()) { + // no body needed + return + } + if (l2.IsPublic()) { + ctx.flowsToPublicCanFlowInternal_DYStar(corruptIds, l1, l3) + return + } + if (l3.IsPublic()) { + ctx.canFlowFlowsToPublic_DYStar(corruptIds, l1, l2) + return + } + + if l1.IsReaders() && l2.IsReaders() && l3.IsReaders() && + includesIds(label.GetReaders(l1), label.GetReaders(l2)) && + includesIds(label.GetReaders(l2), label.GetReaders(l3)) { + ctx.includesIdsTransitive(label.GetReaders(l1), label.GetReaders(l2), label.GetReaders(l3)) + } + + if l1.IsReaders() && l2.IsReaders() && l3.IsReaders() && + ctx.canFlowInternal_DYStar(corruptIds, l1, l2) && + ctx.canFlowInternal_DYStar(corruptIds, l2, l3) && + includesIds(label.GetReaders(l1), label.GetReaders(l2)) && + tr.containsCorruptId(corruptIds, label.GetReaders(l2)) { + ctx.containsCorruptIdMonotonic(corruptIds, label.GetReaders(l2), label.GetReaders(l1)) + } + + if (l1.IsJoin() || l1.IsMeet()) { + l11 := label.GetFirstLabel(l1) + l12 := label.GetSecondLabel(l1) + ctx.canFlowInternalTransitive_DYStar(corruptIds, l11, l2, l3) + ctx.canFlowInternalTransitive_DYStar(corruptIds, l12, l2, l3) + } + if (l2.IsJoin() || l2.IsMeet()) { + l21 := label.GetFirstLabel(l2) + l22 := label.GetSecondLabel(l2) + ctx.canFlowInternalTransitive_DYStar(corruptIds, l1, l21, l3) + ctx.canFlowInternalTransitive_DYStar(corruptIds, l1, l22, l3) + } + if (l3.IsJoin() || l3.IsMeet()) { + l31 := label.GetFirstLabel(l3) + l32 := label.GetSecondLabel(l3) + ctx.canFlowInternalTransitive_DYStar(corruptIds, l1, l2, l31) + ctx.canFlowInternalTransitive_DYStar(corruptIds, l1, l2, l32) + } +} + +ghost +decreases +ensures ctx.CanFlow(t, l1, label.Public()) ==> ctx.CanFlow(t, l1, l2) +func (ctx LabelingContext) FlowsToPublicCanFlow(t tr.TraceEntry, l1, l2 label.SecrecyLabel) { + ctx.flowsToPublicCanFlowInternal(t.getCorruptIds(), l1, l2) +} + +ghost +decreases +ensures ctx.canFlowInternal(corruptIds, l1, label.Public()) ==> ctx.canFlowInternal(corruptIds, l1, l2) +func (ctx LabelingContext) flowsToPublicCanFlowInternal(corruptIds IdSet, l1, l2 label.SecrecyLabel) { + if ctx.canFlowInternal(corruptIds, l1, label.Public()) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l1, label.Public()) + ctx.flowsToPublicCanFlowInternal_DYStar(corruptIds, l1, l2) + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l1, l2) + } +} + +ghost +decreases label.GetHeight(l1), label.GetHeight(l2) +ensures ctx.canFlowInternal_DYStar(corruptIds, l1, label.Public()) ==> ctx.canFlowInternal_DYStar(corruptIds, l1, l2) +func (ctx LabelingContext) flowsToPublicCanFlowInternal_DYStar(corruptIds IdSet, l1, l2 label.SecrecyLabel) { + if (l1.IsJoin() || l1.IsMeet()) { + l11 := label.GetFirstLabel(l1) + l12 := label.GetSecondLabel(l1) + ctx.flowsToPublicCanFlowInternal_DYStar(corruptIds, l11, l2) + ctx.flowsToPublicCanFlowInternal_DYStar(corruptIds, l12, l2) + } + if (l2.IsJoin() || l2.IsMeet()) { + l21 := label.GetFirstLabel(l2) + l22 := label.GetSecondLabel(l2) + ctx.flowsToPublicCanFlowInternal_DYStar(corruptIds, l1, l21) + ctx.flowsToPublicCanFlowInternal_DYStar(corruptIds, l1, l22) + } +} + +ghost +decreases +ensures (ctx.canFlowInternal(corruptIds, l2, label.Public()) && ctx.canFlowInternal(corruptIds, l1, l2)) ==> ctx.canFlowInternal(corruptIds, l1, label.Public()) +func (ctx LabelingContext) canFlowFlowsToPublic(corruptIds IdSet, l1, l2 label.SecrecyLabel) { + if ctx.canFlowInternal(corruptIds, l2, label.Public()) && ctx.canFlowInternal(corruptIds, l1, l2) { + ctx.canFlowInternalImpliesDYStar(corruptIds, l2, label.Public()) + ctx.canFlowInternalImpliesDYStar(corruptIds, l1, l2) + ctx.canFlowFlowsToPublic_DYStar(corruptIds, l1, l2) + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l1, label.Public()) + } +} + +ghost +decreases label.GetHeight(l1), label.GetHeight(l2) +ensures (ctx.canFlowInternal_DYStar(corruptIds, l2, label.Public()) && ctx.canFlowInternal_DYStar(corruptIds, l1, l2)) ==> ctx.canFlowInternal_DYStar(corruptIds, l1, label.Public()) +func (ctx LabelingContext) canFlowFlowsToPublic_DYStar(corruptIds IdSet, l1, l2 label.SecrecyLabel) { + if l1.IsReaders() && l2.IsReaders() && + ctx.canFlowInternal_DYStar(corruptIds, l2, label.Public()) && + ctx.canFlowInternal_DYStar(corruptIds, l1, l2) && + includesIds(label.GetReaders(l1), label.GetReaders(l2)) { + ctx.containsCorruptIdMonotonic(corruptIds, label.GetReaders(l2), label.GetReaders(l1)) + } + if (l1.IsJoin() || l1.IsMeet()) { + l11 := label.GetFirstLabel(l1) + l12 := label.GetSecondLabel(l1) + ctx.canFlowFlowsToPublic_DYStar(corruptIds, l11, l2) + ctx.canFlowFlowsToPublic_DYStar(corruptIds, l12, l2) + } + if (l2.IsJoin() || l2.IsMeet()) { + l21 := label.GetFirstLabel(l2) + l22 := label.GetSecondLabel(l2) + ctx.canFlowFlowsToPublic_DYStar(corruptIds, l1, l21) + ctx.canFlowFlowsToPublic_DYStar(corruptIds, l1, l22) + } +} + +ghost +decreases +requires l1.IsReaders() && l2.IsReaders() +requires label.GetReaders(l2) subset label.GetReaders(l1) +ensures ctx.CanFlow(t, l1, l2) +func (ctx LabelingContext) CanFlowToSubsetReaders(t tr.TraceEntry, l1, l2 label.SecrecyLabel) { + corruptIds := t.getCorruptIds() + ctx.canFlowInternalToSubsetReaders(corruptIds, l1, l2) +} + +ghost +decreases +requires l1.IsReaders() && l2.IsReaders() +requires label.GetReaders(l2) subset label.GetReaders(l1) +ensures ctx.canFlowInternal(corruptIds, l1, l2) +func (ctx LabelingContext) canFlowInternalToSubsetReaders(corruptIds IdSet, l1, l2 label.SecrecyLabel) { + ctx.canFlowInternalToSubsetReaders_DYStar(corruptIds, l1, l2) + ctx.canFlowInternalIsImpliesByDYStar(corruptIds, l1, l2) +} + +ghost +decreases +requires l1.IsReaders() && l2.IsReaders() +requires label.GetReaders(l2) subset label.GetReaders(l1) +ensures ctx.canFlowInternal_DYStar(corruptIds, l1, l2) +func (ctx LabelingContext) canFlowInternalToSubsetReaders_DYStar(corruptIds IdSet, l1, l2 label.SecrecyLabel) { + ids1 := label.GetReaders(l1) + ids2 := label.GetReaders(l2) + ctx.includesIdsSubset(ids1, ids2) +} + +ghost +decreases +requires t1.isSuffix(t2) +requires ctx.CanFlow(t1, l1, l2) +ensures ctx.CanFlow(t2, l1, l2) +func (ctx LabelingContext) CanFlowMonotonic(t1, t2 tr.TraceEntry, l1, l2 label.SecrecyLabel) { + ctx.CanFlowMonotonicInternal(t1, t2, l1, l2) +} + +ghost +decreases label.GetHeight(l1), label.GetHeight(l2) +requires t1.isSuffix(t2) +ensures ctx.CanFlow(t1, l1, l2) ==> ctx.CanFlow(t2, l1, l2) +func (ctx LabelingContext) CanFlowMonotonicInternal(t1, t2 tr.TraceEntry, l1, l2 label.SecrecyLabel) { + if (!l1.IsJoin() && !l1.IsMeet() && !l2.IsJoin() && !l2.IsMeet()) { + t1.getCorruptIdsMonotonic(t2) + } else { + if (l1.IsJoin() || l1.IsMeet()) { + l11 := label.GetFirstLabel(l1) + l12 := label.GetSecondLabel(l1) + ctx.CanFlowMonotonicInternal(t1, t2, l11, l2) + ctx.CanFlowMonotonicInternal(t1, t2, l12, l2) + } + if (l2.IsJoin() || l2.IsMeet()) { + l21 := label.GetFirstLabel(l2) + l22 := label.GetSecondLabel(l2) + ctx.CanFlowMonotonicInternal(t1, t2, l1, l21) + ctx.CanFlowMonotonicInternal(t1, t2, l1, l22) + } + } +} + +ghost +decreases +requires ctx.CanFlow(t, label.Readers(set[p.Id]{ id }), label.Public()) +ensures corruptedId in t.getCorruptIds() && id.Covers(corruptedId) +func (ctx LabelingContext) CanFlowToPublicImpliesCorruption(t tr.TraceEntry, id p.Id) (corruptedId p.Id) { + assert exists corruptedId p.Id :: corruptedId in t.getCorruptIds() && id.Covers(corruptedId) + // get witness + corruptedId := arb.GetArbId() + assume corruptedId in t.getCorruptIds() && id.Covers(corruptedId) +} + +ghost +decreases +requires ids1 subset ids2 || includesIds(ids2, ids1) +requires tr.containsCorruptId(corruptIds, ids1) +ensures tr.containsCorruptId(corruptIds, ids2) +func (ctx LabelingContext) containsCorruptIdMonotonic(corruptIds, ids1, ids2 set[p.Id]) { + assert exists corruptedId p.Id :: { tr.containsId(ids1, corruptedId) } corruptedId in corruptIds && tr.containsId(ids1, corruptedId) + // get witness: + corruptedId := arb.GetArbId() + assume corruptedId in corruptIds && tr.containsId(ids1, corruptedId) + if includesIds(ids2, ids1) { + ctx.containsIdTransitive(ids1, ids2, corruptedId) + } + // the following assert stmt is necessary: + assert tr.containsId(ids2, corruptedId) +} + +ghost +decreases +requires corruptIds1 subset corruptIds2 +requires tr.containsCorruptId(corruptIds1, ids) +ensures tr.containsCorruptId(corruptIds2, ids) +func (ctx LabelingContext) containsCorruptIdMonotonic2(corruptIds1, corruptIds2, ids set[p.Id]) { + assert exists corruptedId p.Id :: { tr.containsId(ids, corruptedId) } corruptedId in corruptIds1 && tr.containsId(ids, corruptedId) + // get witness: + corruptedId := arb.GetArbId() + assume corruptedId in corruptIds1 && tr.containsId(ids, corruptedId) + assert corruptedId in corruptIds2 +} + +ghost +decreases +requires tr.containsCorruptId(corruptIds, ids1 union ids2) +ensures tr.containsCorruptId(corruptIds, ids1) || tr.containsCorruptId(corruptIds, ids2) +func (ctx LabelingContext) containsCorruptIdSplit(corruptIds set[p.Id], ids1, ids2 set[p.Id]) { + ids := ids1 union ids2 + assert exists corruptedId p.Id :: { tr.containsId(ids, corruptedId) } corruptedId in corruptIds && tr.containsId(ids, corruptedId) + // get witness: + corruptedId := arb.GetArbId() + assume corruptedId in corruptIds && tr.containsId(ids, corruptedId) + ctx.containsIdSplit(ids1, ids2, corruptedId) +} + +ghost +decreases +requires tr.containsCorruptId(corruptIds, ids1) || tr.containsCorruptId(corruptIds, ids2) +ensures tr.containsCorruptId(corruptIds, ids1 union ids2) +func (ctx LabelingContext) containsCorruptIdUnion(corruptIds set[p.Id], ids1, ids2 set[p.Id]) { + ids := ids1 union ids2 + if tr.containsCorruptId(corruptIds, ids1) { + ctx.containsCorruptIdMonotonic(corruptIds, ids1, ids) + } else if tr.containsCorruptId(corruptIds, ids2) { + ctx.containsCorruptIdMonotonic(corruptIds, ids2, ids) + } +} + +ghost +decreases +ensures (!tr.containsCorruptId(corruptIds, ids1) && !tr.containsCorruptId(corruptIds, ids2)) == !tr.containsCorruptId(corruptIds, ids1 union ids2) +func (ctx LabelingContext) containsCorruptIdNotUnion(corruptIds set[p.Id], ids1, ids2 set[p.Id]) { + ids := ids1 union ids2 + if !tr.containsCorruptId(corruptIds, ids1) && + !tr.containsCorruptId(corruptIds, ids2) { + // we prove it by contradiction: + if tr.containsCorruptId(corruptIds, ids) { + ctx.containsCorruptIdSplit(corruptIds, ids1, ids2) + } + } + if !tr.containsCorruptId(corruptIds, ids1 union ids2) { + // we prove it by contradiction: + if tr.containsCorruptId(corruptIds, ids1) || + tr.containsCorruptId(corruptIds, ids2) { + ctx.containsCorruptIdUnion(corruptIds, ids1, ids2) + } + } +} + +ghost +decreases +requires tr.containsId(ids1, corruptedId) && includesIds(ids2, ids1) +ensures tr.containsId(ids2, corruptedId) +func (ctx LabelingContext) containsIdTransitive(ids1, ids2 set[p.Id], corruptedId p.Id) { + // apply `containsId(ids1, corruptedId)`: + assert exists id1 p.Id :: { id1.Covers(corruptedId) } id1 in ids1 && id1.Covers(corruptedId) + // get witness: + id1 := arb.GetArbId() + assume id1 in ids1 && id1.Covers(corruptedId) + + // apply `includesIds(ids2, ids1)`: + assert forall id p.Id :: id in ids1 ==> tr.containsId(ids2, id) + assert tr.containsId(ids2, id1) + + // apply `containsId(ids2, id1)`: + assert exists id p.Id :: id in ids2 && id.Covers(id1) + // get witness: + idX := arb.GetArbId() + assume idX in ids2 && idX.Covers(id1) + + idX.CoversTransitive(id1, corruptedId) + assert exists id p.Id :: { id.Covers(corruptedId) } id in ids2 && id.Covers(corruptedId) +} + +ghost +decreases +requires tr.containsId(ids1 union ids2, corruptedId) +ensures tr.containsId(ids1, corruptedId) || tr.containsId(ids2, corruptedId) +func (ctx LabelingContext) containsIdSplit(ids1, ids2 set[p.Id], corruptedId p.Id) { + // no body needed +} + +ghost +decreases +/** + * checks whether every id in `ids` is contained in `haystack` while taking their + * coverings into account. + * This is similar to `containsCorruptId` except that a forall instead of existential + * quantifier is used for every ID contained in the first parameter. + */ +pure func includesIds(haystack, ids set[p.Id]) bool { + return forall id p.Id :: { tr.containsId(haystack, id) } id in ids ==> tr.containsId(haystack, id) +} + +ghost +decreases +ensures includesIds(ids, ids) +func (ctx LabelingContext) includesIdsReflexive(ids set[p.Id]) { + arbId := arb.GetArbId() + if arbId in ids { + arbId.CoversReflexive() + } + // we have shown it for an arbitrary `arbId` and can thus perform forall introduction: + assert arbId in ids ==> tr.containsId(ids, arbId) + assume forall id p.Id :: { tr.containsId(ids, id) } id in ids ==> tr.containsId(ids, id) +} + +ghost +decreases +requires includesIds(ids1, ids2) && includesIds(ids2, ids3) +ensures includesIds(ids1, ids3) +func (ctx LabelingContext) includesIdsTransitive(ids1, ids2, ids3 set[p.Id]) { + assert forall id p.Id :: { tr.containsId(ids1, id) } id in ids2 ==> tr.containsId(ids1, id) + assert forall id p.Id :: { tr.containsId(ids2, id) } id in ids3 ==> tr.containsId(ids2, id) + + arbId := arb.GetArbId() + if arbId in ids3 { + // follows from `includesIds(ids2, ids3)`: + assert tr.containsId(ids2, arbId) + // use additionally the fact that `includesIds(ids1, ids2)`: + ctx.containsIdTransitive(ids2, ids1, arbId) + } + assert arbId in ids3 ==> tr.containsId(ids1, arbId) + // we can do a forall introduction as `arbId` is unconstraint: + assume forall id p.Id :: { tr.containsId(ids1, id) } id in ids3 ==> tr.containsId(ids1, id) +} + +ghost +decreases +requires ids2 subset ids1 +ensures includesIds(ids1, ids2) +func (ctx LabelingContext) includesIdsSubset(ids1, ids2 set[p.Id]) { + arbId := arb.GetArbId() + if arbId in ids2 { + // apply `ids2 subset ids1`: + assert arbId in ids1 + arbId.CoversReflexive() + // `arbId` is itself the witness for `containsId(ids1, arbId)`: + assert tr.containsId(ids1, arbId) + } + assert arbId in ids2 ==> tr.containsId(ids1, arbId) + // perform a forall introduction since `arbId` is unconstraint: + assume forall id p.Id :: { tr.containsId(ids1, id) } id in ids2 ==> tr.containsId(ids1, id) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) GetSkLabel(pk tm.Term) label.SecrecyLabel { + return (pk.IsPk()) ? + ctx.GetLabel(tm.getSk(pk)) : + label.Public() +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) IsSecretKey(t tr.TraceEntry, owner p.Id, sk tm.Term, keyType KeyType, usage string) bool { + return (keyType == KeyTypePke() ==> ctx.IsPrivateDecKey(t, owner, sk, usage)) && + (keyType == KeyTypeDh() ==> ctx.IsPrivateDhKey(t, owner, sk, usage)) && + (keyType == KeyTypeSigning() ==> ctx.IsPrivateSigningKey(t, owner, sk, usage)) +} + +ghost +decreases +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsSecretKey(t1, owner, sk, keyType, usage) +ensures ctx.IsSecretKey(t2, owner, sk, keyType, usage) +func (ctx LabelingContext) IsSecretKeyMonotonic(t1, t2 tr.TraceEntry, owner p.Id, sk tm.Term, keyType KeyType, usage string) bool { + if keyType == KeyTypePke() { + ctx.IsPrivateDecKeyMonotonic(t1, t2, owner, sk, usage) + } else if keyType == KeyTypeDh() { + ctx.IsPrivateDhKeyMonotonic(t1, t2, owner, sk, usage) + } else if keyType == KeyTypeSigning() { + ctx.IsPrivateSigningKeyMonotonic(t1, t2, owner, sk, usage) + } +} + +ghost +decreases +requires ctx.Props() +requires ctx.IsSecretKey(t, owner, sk, keyType, usage) +ensures ctx.IsLabeled(t, sk, label.Readers(set[p.Id]{ owner })) +func (ctx LabelingContext) IsSecretKeyLemma(t tr.TraceEntry, owner p.Id, sk tm.Term, keyType KeyType, usage string) { + // the following assert stmt is needed for triggering reasons: + assert getKeyTypeType(keyType) == 0 || getKeyTypeType(keyType) == 1 || getKeyTypeType(keyType) == 2 +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) IsPublicKey(t tr.TraceEntry, skOwner p.Id, pk, sk tm.Term, keyType KeyType, usage string) bool { + return (keyType == KeyTypePke() ==> ctx.IsPublicEncKey(t, skOwner, pk, sk, usage)) && + (keyType == KeyTypeDh() ==> ctx.IsPublicDhKey(t, skOwner, pk, sk, usage)) && + (keyType == KeyTypeSigning() ==> ctx.IsPublicSigningKey(t, skOwner, pk, sk, usage)) +} + +ghost +decreases +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsPublicKey(t1, skOwner, pk, sk, keyType, usage) +ensures ctx.IsPublicKey(t2, skOwner, pk, sk, keyType, usage) +func (ctx LabelingContext) IsPublicKeyMonotonic(t1, t2 tr.TraceEntry, skOwner p.Id, pk, sk tm.Term, keyType KeyType, usage string) { + if keyType == KeyTypePke() { + ctx.IsPublicEncKeyMonotonic(t1, t2, skOwner, pk, sk, usage) + } else if keyType == KeyTypeDh() { + ctx.IsPublicDhKeyMonotonic(t1, t2, skOwner, pk, sk, usage) + } else if keyType == KeyTypeSigning() { + ctx.IsPublicSigningKeyMonotonic(t1, t2, skOwner, pk, sk, usage) + } +} + +ghost +decreases +requires ctx.Props() +requires ctx.IsPublicKey(t, skOwner, pk, sk, keyType, usageString) +ensures ctx.IsLabeled(t, sk, label.Readers(set[p.Id]{ skOwner })) +func (ctx LabelingContext) IsPublicKeyLemma(t tr.TraceEntry, skOwner p.Id, pk, sk tm.Term, keyType KeyType, usageString string) { + // the following assert stmt is needed for triggering reasons: + assert getKeyTypeType(keyType) == 0 || getKeyTypeType(keyType) == 1 || getKeyTypeType(keyType) == 2 + var usage u.Usage + if keyType == KeyTypePke() { + usage = u.PkeKey(usageString) + } else if keyType == KeyTypeDh() { + usage = u.DhKey(usageString) + } else if keyType == KeyTypeSigning() { + usage = u.SigningKey(usageString) + } + assert ctx.IsSecret(t, sk, label.Readers(set[p.Id]{ skOwner }), usage) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) IsPublicKeyExistential(t tr.TraceEntry, skOwner p.Id, pk tm.Term, keyType KeyType, usage string) bool { + return (keyType == KeyTypePke() ==> exists sk tm.Term :: { ctx.IsPublicEncKey(t, skOwner, pk, sk, usage) } ctx.IsPublicEncKey(t, skOwner, pk, sk, usage)) && + (keyType == KeyTypeDh() ==> ctx.IsPublicDhKeyExistential(t, skOwner, pk, usage)) && + (keyType == KeyTypeSigning() ==> exists sk tm.Term :: { ctx.IsPublicSigningKey(t, skOwner, pk, sk, usage) } ctx.IsPublicSigningKey(t, skOwner, pk, sk, usage)) +} + +ghost +decreases +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsPublicKeyExistential(t1, skOwner, pk, keyType, usage) +ensures ctx.IsPublicKeyExistential(t2, skOwner, pk, keyType, usage) +func (ctx LabelingContext) IsPublicKeyExistentialMonotonic(t1, t2 tr.TraceEntry, skOwner p.Id, pk tm.Term, keyType KeyType, usage string) { + skWitness := arb.GetArbTerm() + if keyType == KeyTypePke() { + assert exists sk tm.Term :: { ctx.IsPublicEncKey(t1, skOwner, pk, sk, usage) } ctx.IsPublicEncKey(t1, skOwner, pk, sk, usage) + // get witness + assume ctx.IsPublicEncKey(t1, skOwner, pk, skWitness, usage) + } else if keyType == KeyTypeDh() { + assert exists sk tm.Term :: { ctx.IsPublicDhKey(t1, skOwner, pk, sk, usage) } ctx.IsPublicDhKey(t1, skOwner, pk, sk, usage) + // get witness + assume ctx.IsPublicDhKey(t1, skOwner, pk, skWitness, usage) + } else if keyType == KeyTypeSigning() { + assert exists sk tm.Term :: { ctx.IsPublicSigningKey(t1, skOwner, pk, sk, usage) } ctx.IsPublicSigningKey(t1, skOwner, pk, sk, usage) + // get witness + assume ctx.IsPublicSigningKey(t1, skOwner, pk, skWitness, usage) + } + ctx.IsPublicKeyMonotonic(t1, t2, skOwner, pk, skWitness, keyType, usage) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) IsPrivateDecKey(t tr.TraceEntry, owner p.Id, sk tm.Term, usage string) bool { + return sk == tm.random(tm.gamma(sk), label.Readers(set[p.Id]{ owner }), u.PkeKey(usage)) && + ctx.IsSecret(t, sk, label.Readers(set[p.Id]{ owner }), u.PkeKey(usage)) +} + +ghost +decreases +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsPrivateDecKey(t1, owner, sk, usage) +ensures ctx.IsPrivateDecKey(t2, owner, sk, usage) +func (ctx LabelingContext) IsPrivateDecKeyMonotonic(t1, t2 tr.TraceEntry, owner p.Id, sk tm.Term, usage string) { + ctx.IsSecretMonotonic(t1, t2, sk, label.Readers(set[p.Id]{ owner }), u.PkeKey(usage)) +} + +ghost +decreases +requires ctx.Props() +// sk is the witness +pure func (ctx LabelingContext) IsPublicEncKey(t tr.TraceEntry, skOwner p.Id, pk, sk tm.Term, usage string) bool { + return ctx.IsPublishable(t, pk) && + ctx.IsPrivateDecKey(t, skOwner, sk, usage) && + pk == tm.createPk(sk) +} + +ghost +decreases +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsPublicEncKey(t1, skOwner, pk ,sk, usage) +ensures ctx.IsPublicEncKey(t2, skOwner, pk ,sk, usage) +func (ctx LabelingContext) IsPublicEncKeyMonotonic(t1, t2 tr.TraceEntry, skOwner p.Id, pk, sk tm.Term, usage string) { + ctx.IsPublishableMonotonic(t1, t2, pk) + ctx.IsPrivateDecKeyMonotonic(t1, t2, skOwner, sk, usage) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) IsPublicEncKeyExistential(t tr.TraceEntry, pk tm.Term, l label.SecrecyLabel, usage string) bool { + return ctx.IsPublishable(t, pk) && + pk.IsPk() && + ctx.IsSecret(t, tm.getSk(pk), l, u.PkeKey(usage)) +} + +ghost +decreases +requires ctx.Props() +// sk is the witness +pure func (ctx LabelingContext) IsPrivateDhKey(t tr.TraceEntry, owner p.Id, sk tm.Term, usage string) bool { + return sk.IsRandom() && + ctx.IsSecret(t, sk, label.Readers(set[p.Id]{ owner }), u.DhKey(usage)) +} + +ghost +decreases +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsPrivateDhKey(t1, owner, sk, usage) +ensures ctx.IsPrivateDhKey(t2, owner, sk, usage) +func (ctx LabelingContext) IsPrivateDhKeyMonotonic(t1, t2 tr.TraceEntry, owner p.Id, sk tm.Term, usage string) { + ctx.IsSecretMonotonic(t1, t2, sk, label.Readers(set[p.Id]{ owner }), u.DhKey(usage)) +} + +ghost +decreases +requires ctx.Props() +// sk is the witness +pure func (ctx LabelingContext) IsPrivateSigningKey(t tr.TraceEntry, owner p.Id, sk tm.Term, usage string) bool { + return sk.IsRandom() && + ctx.IsSecret(t, sk, label.Readers(set[p.Id]{ owner }), u.SigningKey(usage)) +} + +ghost +decreases +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsPrivateSigningKey(t1, owner, sk, usage) +ensures ctx.IsPrivateSigningKey(t2, owner, sk, usage) +func (ctx LabelingContext) IsPrivateSigningKeyMonotonic(t1, t2 tr.TraceEntry, owner p.Id, sk tm.Term, usage string) { + ctx.IsSecretMonotonic(t1, t2, sk, label.Readers(set[p.Id]{ owner }), u.SigningKey(usage)) +} + +ghost +decreases +requires ctx.Props() +// sk is the witness +pure func (ctx LabelingContext) IsPublicDhKey(t tr.TraceEntry, skOwner p.Id, pk, sk tm.Term, usage string) bool { + return ctx.IsPublishable(t, pk) && + pk == tm.exp(tm.generator(), sk) && + ctx.IsPrivateDhKey(t, skOwner, sk, usage) +} + +ghost +decreases +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsPublicDhKey(t1, skOwner, pk, sk, usage) +ensures ctx.IsPublicDhKey(t2, skOwner, pk, sk, usage) +func (ctx LabelingContext) IsPublicDhKeyMonotonic(t1, t2 tr.TraceEntry, skOwner p.Id, pk, sk tm.Term, usage string) { + ctx.IsPublishableMonotonic(t1, t2, pk) + ctx.IsPrivateDhKeyMonotonic(t1, t2, skOwner, sk, usage) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) IsPublicDhKeyExistential(t tr.TraceEntry, skOwner p.Id, pk tm.Term, usage string) bool { + return exists sk tm.Term :: { ctx.IsPublicDhKey(t, skOwner, pk, sk, usage) } ctx.IsPublicDhKey(t, skOwner, pk, sk, usage) +} + +ghost +decreases +requires ctx.Props() +// sk is the witness +pure func (ctx LabelingContext) IsPublicSigningKey(t tr.TraceEntry, skOwner p.Id, pk, sk tm.Term, usage string) bool { + return ctx.IsPublishable(t, pk) && + pk == tm.createPk(sk) && + ctx.IsPrivateSigningKey(t, skOwner, sk, usage) +} + +ghost +decreases +requires ctx.Props() +requires t1.isSuffix(t2) +requires ctx.IsPublicSigningKey(t1, skOwner, pk, sk, usage) +ensures ctx.IsPublicSigningKey(t2, skOwner, pk, sk, usage) +func (ctx LabelingContext) IsPublicSigningKeyMonotonic(t1, t2 tr.TraceEntry, skOwner p.Id, pk, sk tm.Term, usage string) { + ctx.IsPublishableMonotonic(t1, t2, pk) + ctx.IsPrivateSigningKeyMonotonic(t1, t2, skOwner, sk, usage) +} + +ghost +decreases +requires ctx.Props() +requires ctx.IsMsg(t, term, l1) +requires ctx.CanFlow(t, l1, l2) +ensures ctx.IsMsg(t, term, l2) +func (ctx LabelingContext) Restrict(t tr.TraceEntry, term tm.Term, l1, l2 label.SecrecyLabel) { + ctx.CanFlowTransitive(t, ctx.GetLabel(term), l1, l2) +} + +pred (ctx LabelingContext) NonceIsUnique(nonce tm.Term) { + acc(ctx.getNonceIsUniquePointer(nonce)) +} + +pred (ctx LabelingContext) NonceForEventIsUnique(nonce tm.Term, eventType ev.EventType) { + acc(ctx.getNonceForEventIsUniquePointer(nonce, eventType)) +} + +// each nonce term is (injectively) mapped to a heap location for which write access +// is obtained as part of the nonce generation algorithm. +// Pairwise disjointness of nonces / their uniqueness directly follows from this property +// as otherwise the `NonceIsUnique` resource would be stored twice on the trace which immediately +// results in a contradiction when unfolding the predicate instances +type Void int // note that `struct{}` results in Gobra not generating the expected heap permissions +ghost +pure func (ctx LabelingContext) getNonceIsUniquePointer(nonce tm.Term) *Void + +ghost +pure func (ctx LabelingContext) getNonceForEventIsUniquePointer(nonce tm.Term, eventType ev.EventType) *Void + +ghost +decreases +requires ctx.Props() +requires acc(ctx.NonceForEventIsUnique(nonce, eventType), 2/1) +ensures false +func (ctx LabelingContext) NonceForEventContradiction(nonce tm.Term, eventType ev.EventType) { + unfold acc(ctx.NonceForEventIsUnique(nonce, eventType), 2/1) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) CanEncrypt(t tr.TraceEntry, msg, pk tm.Term) bool { + return ctx.IsPublishable(t, pk) && + ctx.IsPkeKey(tm.getSk(pk)) && + ctx.IsMsg(t, msg, ctx.GetSkLabel(pk)) && + (forall usageString string :: { ctx.usage.PkePred(t, usageString, msg, pk) } ctx.IsPublicEncKeyExistential(t, pk, ctx.GetSkLabel(pk), usageString) ==> + ctx.usage.PkePred(t, usageString, msg, pk)) +} + +ghost +decreases +requires ctx.Props() +requires ctx.CanEncrypt(t, msg, pk) || (ctx.IsPublishable(t, msg) && ctx.IsPublishable(t, pk)) +ensures ctx.IsPublishable(t, tm.encrypt(msg, pk)) +func (ctx LabelingContext) CiphertextIsPublishable(t tr.TraceEntry, msg, pk tm.Term) { + msgLabel := ctx.GetLabel(msg) + if ctx.IsPublishable(t, msg) { + ctx.FlowsToPublicCanFlow(t, msgLabel, ctx.GetSkLabel(pk)) + } + // the following assert stmt is necessary: + assert ctx.IsValidEncrypt(t, pk, msg, msgLabel) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) CanDecrypt(t tr.TraceEntry, ciphertext, sk tm.Term, skOwner p.Id) bool { + return ctx.IsPublishable(t, ciphertext) && + (ctx.IsPublishable(t, sk) || exists usageString string :: { ctx.IsPrivateDecKey(t, skOwner, sk, usageString) } ctx.IsPrivateDecKey(t, skOwner, sk, usageString)) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) WasDecrypted(t tr.TraceEntry, msg, sk tm.Term, skOwner p.Id) bool { + return ctx.IsLabeled(t, sk, label.Readers(set[p.Id]{ skOwner })) ==> + (ctx.IsMsg(t, msg, label.Readers(set[p.Id]{ skOwner })) && + (forall usageString string :: { ctx.IsPrivateDecKey(t, skOwner, sk, usageString) } ctx.IsPrivateDecKey(t, skOwner, sk, usageString) ==> + ctx.IsPublishable(t, msg) || ctx.usage.PkePred(t, usageString, msg, tm.createPk(sk)))) +} + +ghost +decreases +requires ctx.Props() +requires ctx.IsPublishable(t, ciphertext) && ctx.IsPublishable(t, sk) +ensures ctx.CanDecrypt(t, ciphertext, sk, skOwner) +func (ctx LabelingContext) CanDecryptWithPublicSk(t tr.TraceEntry, ciphertext, sk tm.Term, skOwner p.Id) { + // no body needed +} + +ghost +decreases +requires ctx.Props() +requires ctx.CanDecrypt(t, tm.encrypt(msg, tm.createPk(sk)), sk, skOwner) +ensures ctx.WasDecrypted(t, msg, sk, skOwner) +func (ctx LabelingContext) DecryptSatisfiesInvariant(t tr.TraceEntry, msg, sk tm.Term, skOwner p.Id) { + pk := tm.createPk(sk) + plaintextLabel := ctx.GetLabel(msg) + skLabel := ctx.GetLabel(sk) + // the following assertion is necessary to derive that `ctx.IsMsg(t, msg, skLabel)` + assert ctx.IsValidEncrypt(t, pk, msg, plaintextLabel) + if (ctx.CanFlow(t, plaintextLabel, label.Public())) { + ctx.CanFlowTransitive(t, plaintextLabel, label.Public(), skLabel) + } +} + +ghost +decreases +requires ctx.Props() +requires ctx.IsPublishable(t, tm.encrypt(msg, tm.createPk(sk))) && ctx.IsPublishable(t, sk) +ensures ctx.IsPublishable(t, msg) +func (ctx LabelingContext) PlaintextIsPublishableForPublicSk(t tr.TraceEntry, msg, sk tm.Term, skOwner p.Id) { + pk := tm.createPk(sk) + plaintextLabel := ctx.GetLabel(msg) + skLabel := ctx.GetLabel(sk) + // the following assertion is necessary to derive that `ctx.IsMsg(t, msg, skLabel)` + assert ctx.IsValidEncrypt(t, pk, msg, plaintextLabel) + ctx.CanFlowTransitive(t, plaintextLabel, skLabel, label.Public()) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) CanAeadEncrypt(t tr.TraceEntry, key, nonce, msg, ad tm.Term, keyL label.SecrecyLabel) bool { + return ctx.IsLabeledRelaxed(t, key, keyL) && + (ctx.IsPublishable(t, key) || exists usageString string :: ctx.IsAeadKey(t, key, keyL, usageString)) && + ctx.IsPublishable(t, nonce) && + ctx.IsMsg(t, msg, keyL) && + ctx.IsPublishable(t, ad) && + (forall usageString string :: ctx.IsAeadKey(t, key, keyL, usageString) ==> ctx.usage.AeadPred(t, usageString, key, nonce, msg, ad)) +} + +ghost +decreases +requires ctx.Props() +requires ctx.CanAeadEncrypt(t, key, nonce, msg, ad, keyL) || (ctx.IsPublishable(t, key) && ctx.IsPublishable(t, nonce) && ctx.IsPublishable(t, msg) && ctx.IsPublishable(t, ad)) +ensures ctx.IsPublishable(t, tm.aead(key, nonce, msg, ad)) +func (ctx LabelingContext) AeadCiphertextIsPublishable(t tr.TraceEntry, key, nonce, msg, ad tm.Term, keyL label.SecrecyLabel) { + msgL := ctx.GetLabel(msg) + if ctx.IsPublishable(t, key) { + if !ctx.IsPublishable(t, msg) { + ctx.CanFlowTransitive(t, keyL, ctx.GetLabel(key), label.Public()) + ctx.CanFlowTransitive(t, msgL, keyL, label.Public()) + } + } else { + usageCtx := ctx.GetUsage(key) + // the following assert stmt would not be necessary: + assert ctx.usage.AeadPred(t, u.GetUsageString(get(usageCtx)), key, nonce, msg, ad) + ctx.CanFlowTransitive(t, msgL, keyL, ctx.GetLabel(key)) + } + // the following assert stmt is necessary: + assert ctx.IsValidAead(t, key, nonce, msg, msgL, ad) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) CanAeadDecrypt(t tr.TraceEntry, key, nonce, ciphertext, ad tm.Term, keyL label.SecrecyLabel) bool { + return true +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) WasAeadDecrypted(t tr.TraceEntry, key, nonce, msg, ad tm.Term, keyL label.SecrecyLabel) bool { + return ((ctx.IsPublishable(t, key) || exists usageString string :: ctx.IsAeadKey(t, key, keyL, usageString)) && + ctx.IsPublishable(t, nonce) && + ctx.IsPublishable(t, tm.aead(key, nonce, msg, ad)) && + ctx.IsPublishable(t, ad)) ==> + (ctx.IsMsg(t, msg, ctx.GetLabel(key)) && + (forall usageString string :: { ctx.IsAeadKey(t, key, keyL, usageString) } ctx.IsAeadKey(t, key, keyL, usageString) ==> + // either the attacker has applied AEAD encryption or AeadPred holds + // in the first case, this requires that all components are known by the attacker (i.e. key, nonce, msg, and ad) + (ctx.IsPublishable(t, key) && ctx.IsPublishable(t, msg)) || ctx.usage.AeadPred(t, usageString, key, nonce, msg, ad))) +} + +ghost +decreases +requires ctx.Props() +requires ctx.IsPublishable(t, key) && ctx.IsPublishable(t, nonce) && ctx.IsPublishable(t, ciphertext) && ctx.IsPublishable(t, ad) +ensures ctx.CanAeadDecrypt(t, key, nonce, ciphertext, ad, keyL) +func (ctx LabelingContext) CanAeadDecryptWithPublishableKey(t tr.TraceEntry, key, nonce, ciphertext, ad tm.Term, keyL label.SecrecyLabel) { + // no body needed +} + +ghost +decreases +requires ctx.Props() +requires ctx.CanAeadDecrypt(t, key, nonce, tm.aead(key, nonce, msg, ad), ad, keyL) +ensures ctx.WasAeadDecrypted(t, key, nonce, msg, ad, keyL) +func (ctx LabelingContext) AeadDecryptSatisfiesInvariant(t tr.TraceEntry, key, nonce, msg, ad tm.Term, keyL label.SecrecyLabel) { + if ((ctx.IsPublishable(t, key) || exists usageString string :: ctx.IsAeadKey(t, key, keyL, usageString)) && + ctx.IsPublishable(t, nonce) && + ctx.IsPublishable(t, tm.aead(key, nonce, msg, ad)) && + ctx.IsPublishable(t, ad)) { + msgL := ctx.GetLabel(msg) + // the following assertion is necessary to derive that `ctx.IsMsg(t, msg, keyL)` + assert ctx.IsValidAead(t, key, nonce, msg, msgL, ad) + if (ctx.CanFlow(t, msgL, label.Public())) { + ctx.CanFlowTransitive(t, msgL, label.Public(), ctx.GetLabel(key)) + } + } +} + +ghost +decreases +requires ctx.Props() +requires ctx.IsPublishable(t, tm.aead(key, nonce, msg, ad)) && ctx.IsPublishable(t, key) && ctx.IsPublishable(t, nonce) && ctx.IsPublishable(t, ad) +ensures ctx.IsPublishable(t, msg) +func (ctx LabelingContext) PlaintextIsPublishableForPublishableKey(t tr.TraceEntry, key, nonce, msg, ad tm.Term, keyL label.SecrecyLabel) { + if ((ctx.IsPublishable(t, key) || exists usageString string :: ctx.IsAeadKey(t, key, keyL, usageString)) && + ctx.IsPublishable(t, nonce) && + ctx.IsPublishable(t, tm.aead(key, nonce, msg, ad)) && + ctx.IsPublishable(t, ad)) { + msgL := ctx.GetLabel(msg) + // the following assertion is necessary to derive that `ctx.IsMsg(t, msg, keyL)` + assert ctx.IsValidAead(t, key, nonce, msg, msgL, ad) + if !ctx.CanFlow(t, msgL, label.Public()) { + ctx.CanFlowTransitive(t, msgL, ctx.GetLabel(key), label.Public()) + } + } +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) CanSign(t tr.TraceEntry, msg, sk tm.Term, skOwner p.Id) bool { + return ctx.IsPublishable(t, msg) && + ctx.IsLabeledRelaxed(t, sk, label.Readers(set[p.Id]{ skOwner })) && + (ctx.IsPublishable(t, sk) || exists usageString string :: ctx.IsSignKey(t, sk, skOwner, usageString)) && + (forall usageString string :: { ctx.usage.SignPred(t, usageString, msg, sk) } ctx.IsSignKey(t, sk, skOwner, usageString) ==> + ctx.usage.SignPred(t, usageString, msg, sk)) +} + +ghost +decreases +requires ctx.Props() +requires ctx.CanSign(t, msg, sk, skOwner) || (ctx.IsPublishable(t, msg) && ctx.IsPublishable(t, sk)) +ensures ctx.IsPublishable(t, tm.sign(msg, sk)) +func (ctx LabelingContext) SignedMessageIsPublishable(t tr.TraceEntry, msg, sk tm.Term, skOwner p.Id) { + msgL := ctx.GetLabel(msg) + if !ctx.IsPublishable(t, sk) { + usageCtx := ctx.GetUsage(sk) + // the following assert stmt would not be necessary: + assert ctx.usage.SignPred(t, u.GetUsageString(get(usageCtx)), msg, sk) + } + // the following assert stmt is necessary: + assert ctx.IsValidSignature(t, msg, sk) +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) CanOpen(t tr.TraceEntry, signedData, pk tm.Term, skOwner p.Id) bool { + return true +} + +ghost +decreases +requires ctx.Props() +pure func (ctx LabelingContext) WasOpened(t tr.TraceEntry, msg, sk tm.Term, skOwner p.Id) bool { + return ctx.IsValid(t, tm.sign(msg, sk)) ==> + (forall usageString string :: { ctx.IsSignKey(t, sk, skOwner, usageString) } ctx.IsSignKey(t, sk, skOwner, usageString) ==> + // either the attacker has signed ... + (ctx.IsPublishable(t, sk) && ctx.IsPublishable(t, msg)) || + // ... or SignPred holds + ctx.usage.SignPred(t, usageString, msg, sk)) +} + +ghost +decreases +requires ctx.Props() +requires ctx.IsPublishable(t, signedData) && ctx.IsPublishable(t, pk) +ensures ctx.CanOpen(t, signedData, pk, skOwner) +func (ctx LabelingContext) CanOpenWithPublishableKey(t tr.TraceEntry, signedData, pk tm.Term, skOwner p.Id) { + // no body needed +} + +ghost +decreases +requires ctx.Props() +requires ctx.CanOpen(t, tm.sign(msg, sk), tm.createPk(sk), skOwner) +ensures ctx.WasOpened(t, msg, sk, skOwner) +func (ctx LabelingContext) OpenSatisfiesInvariant(t tr.TraceEntry, msg, sk tm.Term, skOwner p.Id) { + // the following assertion is necessary: + if ctx.IsValid(t, tm.sign(msg, sk)) { + assert ctx.IsValidSignature(t, msg, sk) + } +} + +ghost +decreases +requires ctx.Props() +requires ctx.IsPublishable(t, tm.sign(msg, sk)) +ensures ctx.IsPublishable(t, msg) +func (ctx LabelingContext) UnsignedDataIsPublishableIfSignatureIsPublishable(t tr.TraceEntry, msg, sk tm.Term) { + // the following assertion is necessary: + assert ctx.IsValidSignature(t, msg, sk) +} + +ghost +decreases +requires ctx.Props() +requires ctx.IsLabeled(t, term, l) +requires l.IsReaders() +requires !tr.containsCorruptId(t.getCorruptIds(), label.GetReaders(l)) +ensures !ctx.IsPublishable(t, term) +func (ctx LabelingContext) PublishableRequiresCorruption(t tr.TraceEntry, term tm.Term, l label.SecrecyLabel) { + // no body needed +} + +ghost +decreases +requires ctx.Props() +requires ctx.IsLabeledRelaxed(t, term, l) +requires l.IsReaders() +requires !tr.containsCorruptId(t.getCorruptIds(), label.GetReaders(l)) +ensures !ctx.IsPublishable(t, term) +/** + * weakened version of `PublishableRequiresCorruption`: instead of requiring `term` being + * labeled with a `Readers` label, we can permit any label to which `Readers` (i.e. `l`) flows to + */ +func (ctx LabelingContext) PublishableRequiresCorruptionWeakened(t tr.TraceEntry, term tm.Term, l label.SecrecyLabel) { + // proof by contradiction: + // we assume that `l` flows to public and show then that term's actual label would flow + // to public as well, which contradicts the precondition that `l` does not + // contain any corrupted IDs: + termL := ctx.GetLabel(term) + if ctx.CanFlow(t, termL, label.Public()) { + ctx.CanFlowTransitive(t, l, termL, label.Public()) + // this is a contradiction as l would flow to public! + } +} + +ghost +decreases +requires ctx.Props() +requires ctx.IsLabeledRelaxed(t, term, l) +requires l.IsReaders() +requires ctx.IsPublishable(t, term) +ensures tr.containsCorruptId(t.getCorruptIds(), label.GetReaders(l)) +ensures corruptedId in t.getCorruptIds() +ensures id in label.GetReaders(l) && id.Covers(corruptedId) +func (ctx LabelingContext) PublishableImpliesCorruption(t tr.TraceEntry, term tm.Term, l label.SecrecyLabel) (corruptedId, id p.Id) { + termL := ctx.GetLabel(term) + readers := label.GetReaders(l) + corruptIds := t.getCorruptIds() + ctx.CanFlowTransitive(t, l, termL, label.Public()) + // apply `containsCorruptId(corruptIds, readers)`: + assert exists corruptedId p.Id :: { tr.containsId(readers, corruptedId) } corruptedId in corruptIds && tr.containsId(readers, corruptedId) + // get witness + corruptedId = arb.GetArbId() + assume corruptedId in corruptIds && tr.containsId(readers, corruptedId) + // apply `containsId(readers, corruptedId)`: + assert exists id p.Id :: { id.Covers(corruptedId) } id in readers && id.Covers(corruptedId) + // get witness + id = arb.GetArbId() + assume id in readers && id.Covers(corruptedId) +} + +ghost +decreases +requires ctx.Props() +requires ctx.IsMsg(t, term, l) +requires l.IsReaders() +requires !ctx.IsPublishable(t, term) +ensures !tr.containsCorruptId(t.getCorruptIds(), label.GetReaders(l)) +func (ctx LabelingContext) NotPublishableImpliesNoCorruption(t tr.TraceEntry, term tm.Term, l label.SecrecyLabel) { + if ctx.CanFlow(t, l, label.Public()) { + // derive a contradiction: + ctx.CanFlowTransitive(t, ctx.GetLabel(term), l, label.Public()) + } +} + +ghost +decreases +requires ctx.Props() +requires l1.IsReaders() && l2.IsReaders() +requires ctx.IsLabeledRelaxed(t, term, label.Join(l1, l2)) +ensures optCorruptedId == none[p.Id] ? + !ctx.IsPublishable(t, term) : + ((tr.containsId(label.GetReaders(l1), get(optCorruptedId)) || tr.containsId(label.GetReaders(l2), get(optCorruptedId))) && + get(optCorruptedId) in t.getCorruptIds() && + tr.containsCorruptId(t.getCorruptIds(), label.GetReaders(l1) union label.GetReaders(l2))) +func (ctx LabelingContext) RelaxedLabelingImpliesNotPublishableOrCorruption(t tr.TraceEntry, term tm.Term, l1, l2 label.SecrecyLabel) (optCorruptedId option[p.Id]) { + termL := ctx.GetLabel(term) + if ctx.IsPublishable(t, term) { + joinL := label.Join(l1, l2) + ctx.CanFlowTransitive(t, joinL, termL, label.Public()) + // the following assert stmt is necessary: + assert ctx.CanFlow(t, l1, label.Public()) || ctx.CanFlow(t, l2, label.Public()) + if ctx.CanFlow(t, l1, label.Public()) { + assert exists corruptedId p.Id :: { tr.containsId(label.GetReaders(l1), corruptedId) } corruptedId in t.getCorruptIds() && tr.containsId(label.GetReaders(l1), corruptedId) + corruptedId := arb.GetArbId() + assume corruptedId in t.getCorruptIds() && tr.containsId(label.GetReaders(l1), corruptedId) + optCorruptedId = some(corruptedId) + } else { + assert exists corruptedId p.Id :: { tr.containsId(label.GetReaders(l2), corruptedId) } corruptedId in t.getCorruptIds() && tr.containsId(label.GetReaders(l2), corruptedId) + corruptedId := arb.GetArbId() + assume corruptedId in t.getCorruptIds() && tr.containsId(label.GetReaders(l2), corruptedId) + optCorruptedId = some(corruptedId) + } + ctx.containsCorruptIdNotUnion(t.getCorruptIds(), label.GetReaders(l1), label.GetReaders(l2)) + } else { + optCorruptedId = none[p.Id] + } +} diff --git a/ReusableVerificationLibrary/labeling/session-lemmas.gobra b/ReusableVerificationLibrary/labeling/session-lemmas.gobra new file mode 100644 index 0000000..0a3f50c --- /dev/null +++ b/ReusableVerificationLibrary/labeling/session-lemmas.gobra @@ -0,0 +1,195 @@ +package labeling + +import "github.com/viperproject/ReusableProtocolVerificationLibrary/label" +import p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" +import tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" +import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" +import u "github.com/viperproject/ReusableProtocolVerificationLibrary/usage" + + +ghost +decreases +requires ctx.Props() +// the required label is Meet(bJoinAL, bJoinAL) +requires ctx.IsLabeledRelaxed(snapshot, term, label.Join(label.Readers(set[p.Id]{ bId }), label.Readers(set[p.Id]{ aId }))) +ensures ctx.IsLabeledRelaxed(snapshot, term, label.Readers(set[p.Id]{ aId, bId })) +func (ctx LabelingContext) SimplifyJoinToReaders(snapshot tr.TraceEntry, term tm.Term, aId, bId p.Id) { + bothL := label.Readers(set[p.Id]{ aId, bId }) + bJoinAL := label.Join(label.Readers(set[p.Id]{ bId }), label.Readers(set[p.Id]{ aId })) + actualL := ctx.GetLabel(term) + ctx.CanFlowToSubsetReaders(snapshot, bothL, label.Readers(set[p.Id]{ aId })) + ctx.CanFlowToSubsetReaders(snapshot, bothL, label.Readers(set[p.Id]{ bId })) + ctx.CanFlowTransitive(snapshot, bothL, bJoinAL, actualL) + // note that bJoinAL does not flow to bothL +} + +ghost +decreases +requires ctx.Props() +// the required label is Meet(bJoinAL, bJoinAL) +requires ctx.IsLabeledRelaxed(snapshot, term, label.Join(label.Readers(readers1), label.Readers(readers2))) +ensures ctx.IsLabeledRelaxed(snapshot, term, label.Readers(readers1 union readers2)) +func (ctx LabelingContext) SimplifyJoinToReadersSet(snapshot tr.TraceEntry, term tm.Term, readers1, readers2 set[p.Id]) { + bothL := label.Readers(readers1 union readers2) + bJoinAL := label.Join(label.Readers(readers1), label.Readers(readers2)) + actualL := ctx.GetLabel(term) + ctx.CanFlowToSubsetReaders(snapshot, bothL, label.Readers(readers1)) + ctx.CanFlowToSubsetReaders(snapshot, bothL, label.Readers(readers2)) + ctx.CanFlowTransitive(snapshot, bothL, bJoinAL, actualL) + // note that bJoinAL does not flow to bothL +} + +ghost +decreases +requires ctx.Props() +requires ctx.IsSecretPrecise(snapshot, term, label.Meet(l, l), usage) +ensures ctx.IsSecretPrecise(snapshot, term, l, usage) +func (ctx LabelingContext) SimplifySymMeet(snapshot tr.TraceEntry, term tm.Term, l label.SecrecyLabel, usage u.Usage) { + inputL := label.Meet(l, l) + actualL := ctx.GetLabel(term) + ctx.CanFlowReflexive(snapshot, l) + ctx.CanFlowTransitive(snapshot, actualL, inputL, l) +} + +ghost +decreases +requires ctx.Props() +requires ctx.CanFlow(snapshot, l2, l1) +requires ctx.IsLabeledPrecise(snapshot, term, label.Meet(l1, l2)) +ensures ctx.IsLabeledPrecise(snapshot, term, l1) +func (ctx LabelingContext) SimplifySimilarMeet(snapshot tr.TraceEntry, term tm.Term, l1, l2 label.SecrecyLabel) { + inputL := label.Meet(l1, l2) + actualL := ctx.GetLabel(term) + ctx.CanFlowReflexive(snapshot, l1) + ctx.CanFlowCreateMeetLhs(snapshot, l1, l2, l1) + ctx.CanFlowTransitive(snapshot, actualL, inputL, l1) + ctx.CanFlowCreateMeetRhs(snapshot, l1, l1, l2) + ctx.CanFlowTransitive(snapshot, l1, inputL, actualL) +} + +ghost +decreases +ensures ctx.CanFlow(snapshot, label.Readers(set[p.Id]{ sessId.getPrincipalId() }), label.Readers(set[p.Id]{ sessId })) +func (ctx LabelingContext) PrincipalFlowsToSession(snapshot tr.TraceEntry, sessId p.Id) { + // the following assert stmt is necessary: + assert sessId.getPrincipalId().Covers(sessId) +} + +ghost +decreases +ensures ctx.CanFlow(snapshot, label.Readers(set[p.Id]{ aSessId.getPrincipalId(), bSessId }), label.Readers(set[p.Id]{ aSessId, bSessId })) +ensures ctx.CanFlow(snapshot, label.Readers(set[p.Id]{ aSessId, bSessId.getPrincipalId() }), label.Readers(set[p.Id]{ aSessId, bSessId })) +ensures ctx.CanFlow(snapshot, label.Readers(set[p.Id]{ aSessId.getPrincipalId(), bSessId.getPrincipalId() }), label.Readers(set[p.Id]{ aSessId, bSessId })) +func (ctx LabelingContext) PrincipalsFlowsToSession(snapshot tr.TraceEntry, aSessId, bSessId p.Id) { + ctx.PrincipalsFlowsToSession1(snapshot, aSessId, bSessId) + ctx.PrincipalsFlowsToSession1(snapshot, bSessId, aSessId) + assert set[p.Id]{ aSessId, bSessId.getPrincipalId() } == set[p.Id]{ bSessId.getPrincipalId(), aSessId } + ctx.PrincipalsFlowsToSession2(snapshot, aSessId, bSessId) +} + +ghost +decreases +ensures ctx.CanFlow(snapshot, label.Readers(set[p.Id]{ aSessId.getPrincipalId(), bSessId }), label.Readers(set[p.Id]{ aSessId, bSessId })) +func (ctx LabelingContext) PrincipalsFlowsToSession1(snapshot tr.TraceEntry, aSessId, bSessId p.Id) { + ctx.PrincipalsIncludeSessions(aSessId, bSessId) +} + +ghost +decreases +ensures ctx.CanFlow(snapshot, label.Readers(set[p.Id]{ aSessId.getPrincipalId(), bSessId.getPrincipalId() }), label.Readers(set[p.Id]{ aSessId, bSessId })) +func (ctx LabelingContext) PrincipalsFlowsToSession2(snapshot tr.TraceEntry, aSessId, bSessId p.Id) { + ctx.PrincipalsIncludeSessions(aSessId, bSessId) +} + +ghost +decreases +ensures includesIds(set[p.Id]{ aSessId.getPrincipalId(), bSessId }, set[p.Id]{ aSessId, bSessId }) +ensures includesIds(set[p.Id]{ aSessId.getPrincipalId(), bSessId.getPrincipalId() }, set[p.Id]{ aSessId, bSessId }) +func (ctx LabelingContext) PrincipalsIncludeSessions(aSessId, bSessId p.Id) { + // these steps are inspired by `includesIdsSubset` + aSessId.getPrincipalId().CoversReflexive() + bSessId.CoversReflexive() + assert aSessId.getPrincipalId().Covers(aSessId) + assert bSessId.getPrincipalId().Covers(bSessId) + aSessId.CoversReflexive() +} + +ghost +decreases +ensures lhsL == label.Join(label.Readers(set[p.Id]{ aSessId.getPrincipalId() }), label.Readers(set[p.Id]{ bId })) +ensures rhsL == label.Join(label.Readers(set[p.Id]{ aSessId }), label.Readers(set[p.Id]{ bId })) +ensures ctx.CanFlow(snapshot, lhsL, rhsL) +ensures ctx.IsJoinOfReaders(lhsL) && ctx.IsJoinOfReaders(rhsL) +ensures ctx.ReaderUnionFromJoin(lhsL) == label.Readers(set[p.Id]{ aSessId.getPrincipalId(), bId }) +ensures ctx.ReaderUnionFromJoin(rhsL) == label.Readers(set[p.Id]{ aSessId, bId }) +func (ctx LabelingContext) PrincipalsJoinFlowsToSessions(snapshot tr.TraceEntry, aSessId, bId p.Id) (lhsL, rhsL label.SecrecyLabel) { + aL := label.Readers(set[p.Id]{ aSessId.getPrincipalId() }) + aSessL := label.Readers(set[p.Id]{ aSessId }) + bL := label.Readers(set[p.Id]{ bId }) + lhsL = label.Join(aL, bL) + rhsL = label.Join(aSessL, bL) + ctx.PrincipalFlowsToSession(snapshot, aSessId) + ctx.CanFlowReflexive(snapshot, bL) + ctx.CanFlowCreateJoinBoth(snapshot, aL, aSessL, bL, bL) + // the following lemma calls are necessary to show that the postconditions + // involving `ReaderUnionFromJoin` hold: + ctx.ShowReaderUnionFromJoin(aSessId.getPrincipalId(), bId) + ctx.ShowReaderUnionFromJoin(aSessId, bId) +} + +ghost +decreases +ensures lhsL == label.Join(label.Readers(set[p.Id]{ aSessId }), label.Readers(set[p.Id]{ bSessId.getPrincipalId() })) +ensures rhsL == label.Join(label.Readers(set[p.Id]{ aSessId }), label.Readers(set[p.Id]{ bSessId })) +ensures ctx.CanFlow(snapshot, lhsL, rhsL) +ensures ctx.IsJoinOfReaders(lhsL) && ctx.IsJoinOfReaders(rhsL) +ensures ctx.ReaderUnionFromJoin(lhsL) == label.Readers(set[p.Id]{ aSessId, bSessId.getPrincipalId() }) +ensures ctx.ReaderUnionFromJoin(rhsL) == label.Readers(set[p.Id]{ aSessId, bSessId }) +func (ctx LabelingContext) PrincipalsJoinFlowsToBSessions(snapshot tr.TraceEntry, aSessId, bSessId p.Id) (lhsL, rhsL label.SecrecyLabel) { + aSessL := label.Readers(set[p.Id]{ aSessId }) + bL := label.Readers(set[p.Id]{ bSessId.getPrincipalId() }) + bSessL := label.Readers(set[p.Id]{ bSessId }) + lhsL = label.Join(aSessL, bL) + rhsL = label.Join(aSessL, bSessL) + ctx.PrincipalFlowsToSession(snapshot, bSessId) + ctx.CanFlowReflexive(snapshot, aSessL) + ctx.CanFlowCreateJoinBoth(snapshot, aSessL, aSessL, bL, bSessL) + // the following lemma calls are necessary to show that the postconditions + // involving `ReaderUnionFromJoin` hold: + ctx.ShowReaderUnionFromJoin(aSessId, bSessId.getPrincipalId()) + ctx.ShowReaderUnionFromJoin(aSessId, bSessId) +} + +ghost +decreases +ensures ctx.ReaderUnionFromJoin(label.Join(label.Readers(set[p.Id]{ id1 }), label.Readers(set[p.Id]{ id2 }))) == label.Readers(set[p.Id]{ id1, id2 }) +func (ctx LabelingContext) ShowReaderUnionFromJoin(id1, id2 p.Id) { + lhsL := label.Readers(set[p.Id]{ id1 }) + rhsL := label.Readers(set[p.Id]{ id2 }) + joinL := label.Join(lhsL, rhsL) + if label.GetFirstLabel(joinL) == lhsL { + assert set[p.Id]{ id1, id2 } == set[p.Id]{ id1 } union set[p.Id]{ id2 } + } else { + assert label.GetFirstLabel(joinL) == rhsL + assert label.GetSecondLabel(joinL) == lhsL + assert set[p.Id]{ id2, id1 } == set[p.Id]{ id2 } union set[p.Id]{ id1 } + assert set[p.Id]{ id1, id2 } == set[p.Id]{ id2, id1 } + } +} + +ghost +decreases +/** helper function that checks whether l == Join(Readers(set1), Readers(set2)) for some set1 and set2 */ +pure func (ctx LabelingContext) IsJoinOfReaders(l label.SecrecyLabel) bool { + return l.IsJoin() && + label.GetFirstLabel(l).IsReaders() && + label.GetSecondLabel(l).IsReaders() +} + +ghost +decreases +requires ctx.IsJoinOfReaders(l) +/** helper function to convert l == Join(Readers(set1), Readers(set2)) for some set1 and set2 to Readers(set1 union set2) */ +pure func (ctx LabelingContext) ReaderUnionFromJoin(l label.SecrecyLabel) label.SecrecyLabel { + return label.Readers(label.GetReaders(label.GetFirstLabel(l)) union label.GetReaders(label.GetSecondLabel(l))) +} diff --git a/ReusableVerificationLibrary/principal/principal.go b/ReusableVerificationLibrary/principal/principal.go new file mode 100644 index 0000000..0dd1fc6 --- /dev/null +++ b/ReusableVerificationLibrary/principal/principal.go @@ -0,0 +1,153 @@ +package principal + +type Principal = string + +/*@ +type Id domain { + // constructors + // type 0 + func principalId(Principal) Id + // type 1 + func sessionId(Principal, uint32) Id + // type 2; similar to type1 but allows to distinguish between + // different threads belonging to the same session + func sessionThreadId(Principal, uint32, uint32) Id + // type 3 + func stepId(Principal, uint32, uint32) Id + + // deconstructors + func getIdType(Id) int + func getIdPrincipal(Id) Principal + func getIdSession(Id) uint32 + func getIdThread(Id) uint32 + func getIdStep(Id) uint32 + + // WARNING: adapt first axiom if another Id is added! + + axiom { // every Id belongs to a known type + forall id Id :: { getIdType(id) } 0 <= getIdType(id) && getIdType(id) <= 3 + } + + axiom { // principalId is injective + forall principal Principal :: { principalId(principal) } getIdType(principalId(principal)) == 0 && + getIdPrincipal(principalId(principal)) == principal + } + axiom { // principalId implies its constructions + forall id Id :: { getIdType(id) } getIdType(id) == 0 ==> + id == principalId(getIdPrincipal(id)) + } + + axiom { // sessionId is injective + forall principal Principal, session uint32 :: { sessionId(principal, session) } getIdType(sessionId(principal, session)) == 1 && + getIdPrincipal(sessionId(principal, session)) == principal && + getIdSession(sessionId(principal, session)) == session + } + axiom { // sessionId implies its constructions + forall id Id :: { getIdType(id) } getIdType(id) == 1 ==> + id == sessionId(getIdPrincipal(id), getIdSession(id)) + } + + axiom { // sessionThreadId is injective + forall principal Principal, session, threadId uint32 :: { sessionThreadId(principal, session, threadId) } getIdType(sessionThreadId(principal, session, threadId)) == 2 && + getIdPrincipal(sessionThreadId(principal, session, threadId)) == principal && + getIdSession(sessionThreadId(principal, session, threadId)) == session && + getIdThread(sessionThreadId(principal, session, threadId)) == threadId + } + axiom { // sessionThreadId implies its constructions + forall id Id :: { getIdType(id) } getIdType(id) == 2 ==> + id == sessionThreadId(getIdPrincipal(id), getIdSession(id), getIdThread(id)) + } + + axiom { // stepId is injective + forall principal Principal, session, step uint32 :: { stepId(principal, session, step) } getIdType(stepId(principal, session, step)) == 3 && + getIdPrincipal(stepId(principal, session, step)) == principal && + getIdSession(stepId(principal, session, step)) == session && + getIdStep(stepId(principal, session, step)) == step + } + axiom { // stepId implies its constructions + forall id Id :: { getIdType(id) } getIdType(id) == 3 ==> + id == stepId(getIdPrincipal(id), getIdSession(id), getIdStep(id)) + } +} + +// TODO this should be ghost +decreases +ensures res == principalId(principal) +pure func NewPrincipalId(principal Principal) (res Id) + +ghost +decreases +pure func (id Id) IsPrincipal() bool { + return getIdType(id) == 0 +} + +ghost +decreases +pure func (id Id) IsSession() bool { + return getIdType(id) == 1 +} + +ghost +decreases +pure func (id Id) IsSessionThread() bool { + return getIdType(id) == 2 +} + +ghost +decreases +pure func (id Id) IsVersion() bool { + return getIdType(id) == 3 +} + +ghost +decreases +pure func (id Id) getPrincipal() Principal { + return getIdPrincipal(id) +} + +ghost +decreases +pure func (id Id) getSession() option[uint32] { + return id.IsPrincipal() ? none[uint32] : + some(getIdSession(id)) +} + +ghost +decreases +pure func (id Id) getPrincipalId() Id { + return principalId(id.getPrincipal()) +} + +ghost +decreases +// returns true iff `id1` covers `id2` meaning that versions covered by `id1` +// is equal or a superset of the versions covered by `id2` +pure func (id1 Id) Covers(id2 Id) bool { + return id1.IsPrincipal() ? (id1.getPrincipal() == id2.getPrincipal()) : + id1.IsSession() || id1.IsSessionThread() ? (id1.getPrincipal() == id2.getPrincipal() && + id1.getSession() == id2.getSession()) : + id1 == id2 // if id1 is a version +} + +ghost +decreases +ensures id1.Covers(id1) +func (id1 Id) CoversReflexive() { + // no body needed +} + +ghost +decreases +requires id1.Covers(id2) && id2.Covers(id3) +ensures id1.Covers(id3) +func (id1 Id) CoversTransitive(id2, id3 Id) { + // no body needed +} + +ghost +decreases +ensures id.getPrincipalId().Covers(id) +func (id Id) CoveredByPrincipal() { + // no body needed +} +@*/ diff --git a/ReusableVerificationLibrary/term/bytes.gobra b/ReusableVerificationLibrary/term/bytes.gobra new file mode 100644 index 0000000..4f5174a --- /dev/null +++ b/ReusableVerificationLibrary/term/bytes.gobra @@ -0,0 +1,133 @@ +package term + +type Bytes domain { + + // constructors + func tuple2B(Bytes, Bytes) Bytes + func tuple3B(Bytes, Bytes, Bytes) Bytes + func tuple4B(Bytes, Bytes, Bytes, Bytes) Bytes + func tuple5B(Bytes, Bytes, Bytes, Bytes, Bytes) Bytes + func tuple7B(Bytes, Bytes, Bytes, Bytes, Bytes, Bytes, Bytes) Bytes + func hashB(Bytes) Bytes + func kdf1B(Bytes) Bytes + func kdf2B(Bytes) Bytes + func kdf3B(Bytes) Bytes + func createPkB(Bytes) Bytes + func encryptB(Bytes, Bytes) Bytes + func aeadB(Bytes, Bytes, Bytes, Bytes) Bytes + func const1B() Bytes + func expB(Bytes, Bytes) Bytes + func multB(Bytes, Bytes) Bytes + + func stringB(string) Bytes + func zeroStringB(int) Bytes + func integer64B(uint64) Bytes + func integer32B(uint32) Bytes + func infoBytesB() Bytes + func prologueBytesB() Bytes + func generatorB() Bytes + + func signB(Bytes, Bytes) Bytes + + + // deconstructors + func getTupleElemB(Bytes, int) Bytes + func decryptB(Bytes, Bytes) Bytes + func aeadDecryptB(Bytes, Bytes, Bytes) Bytes + func invB(Bytes) Bytes + func verifyB(Bytes, Bytes) bool + func getMsgB(Bytes) Bytes + + + // tuple2B deconstructors + axiom { + forall t1, t2 Bytes :: { tuple2B(t1, t2) } getTupleElemB(tuple2B(t1, t2), 0) == t1 && + getTupleElemB(tuple2B(t1, t2), 1) == t2 + } + + // tuple3B deconstructors + axiom { + forall t1, t2, t3 Bytes :: { tuple3B(t1, t2, t3) } getTupleElemB(tuple3B(t1, t2, t3), 0) == t1 && + getTupleElemB(tuple3B(t1, t2, t3), 1) == t2 && + getTupleElemB(tuple3B(t1, t2, t3), 2) == t3 + } + + // tuple4B deconstructors + axiom { + forall t1, t2, t3, t4 Bytes :: { tuple4B(t1, t2, t3, t4) } getTupleElemB(tuple4B(t1, t2, t3, t4), 0) == t1 && + getTupleElemB(tuple4B(t1, t2, t3, t4), 1) == t2 && + getTupleElemB(tuple4B(t1, t2, t3, t4), 2) == t3 && + getTupleElemB(tuple4B(t1, t2, t3, t4), 3) == t4 + } + + // tuple5B deconstructors + axiom { + forall t1, t2, t3, t4, t5 Bytes :: { tuple5B(t1, t2, t3, t4, t5) } getTupleElemB(tuple5B(t1, t2, t3, t4, t5), 0) == t1 && + getTupleElemB(tuple5B(t1, t2, t3, t4, t5), 1) == t2 && + getTupleElemB(tuple5B(t1, t2, t3, t4, t5), 2) == t3 && + getTupleElemB(tuple5B(t1, t2, t3, t4, t5), 3) == t4 && + getTupleElemB(tuple5B(t1, t2, t3, t4, t5), 4) == t5 + } + + // tuple7B deconstructors + axiom { + forall t1, t2, t3, t4, t5, t6, t7 Bytes :: { tuple7B(t1, t2, t3, t4, t5, t6, t7) } getTupleElemB(tuple7B(t1, t2, t3, t4, t5, t6, t7), 0) == t1 && + getTupleElemB(tuple7B(t1, t2, t3, t4, t5, t6, t7), 1) == t2 && + getTupleElemB(tuple7B(t1, t2, t3, t4, t5, t6, t7), 2) == t3 && + getTupleElemB(tuple7B(t1, t2, t3, t4, t5, t6, t7), 3) == t4 && + getTupleElemB(tuple7B(t1, t2, t3, t4, t5, t6, t7), 4) == t5 && + getTupleElemB(tuple7B(t1, t2, t3, t4, t5, t6, t7), 5) == t6 && + getTupleElemB(tuple7B(t1, t2, t3, t4, t5, t6, t7), 6) == t7 + } + + // decryptB encryptB with matching key + axiom { + forall plaintext, secretKey Bytes :: { decryptB(encryptB(plaintext, createPkB(secretKey)), secretKey) } decryptB(encryptB(plaintext, createPkB(secretKey)), secretKey) == plaintext + } + + // aeadDecryptB aeadB with same key and nonce + axiom { + forall key, nonce, plaintext, additionalData Bytes :: { aeadDecryptB(key, nonce, aeadB(key, nonce, plaintext, additionalData)) } aeadDecryptB(key, nonce, aeadB(key, nonce, plaintext, additionalData)) == plaintext + } + + // signB deconstructors + axiom { + forall msg, sk Bytes :: { verifyB(signB(msg, sk), createPkB(sk)) } { getMsgB(signB(msg, sk)) } verifyB(signB(msg, sk), createPkB(sk)) && getMsgB(signB(msg, sk)) == msg + } +} + +type Gamma domain { + func gamma(Term) Bytes + + func oneTerm(Bytes) Term + + axiom { // totality + forall b Bytes :: {oneTerm(b)} gamma(oneTerm(b)) == b + } + + axiom { // homomorphism + (forall t1, t2 Term :: {gamma(tuple2(t1,t2))} gamma(tuple2(t1,t2)) == tuple2B(gamma(t1), gamma(t2))) && + (forall t1, t2, t3 Term :: {gamma(tuple3(t1,t2,t3))} gamma(tuple3(t1,t2,t3)) == tuple3B(gamma(t1), gamma(t2), gamma(t3))) && + (forall t1, t2, t3, t4 Term :: {gamma(tuple4(t1,t2,t3,t4))} gamma(tuple4(t1,t2,t3,t4)) == tuple4B(gamma(t1), gamma(t2), gamma(t3), gamma(t4))) && + (forall t1, t2, t3, t4, t5 Term :: {gamma(tuple5(t1,t2,t3,t4,t5))} gamma(tuple5(t1,t2,t3,t4,t5)) == tuple5B(gamma(t1), gamma(t2), gamma(t3), gamma(t4), gamma(t5))) && + (forall t1, t2, t3, t4, t5, t6, t7 Term :: {gamma(tuple7(t1,t2,t3,t4,t5,t6,t7))} gamma(tuple7(t1,t2,t3,t4,t5,t6,t7)) == tuple7B(gamma(t1), gamma(t2), gamma(t3), gamma(t4), gamma(t5), gamma(t6), gamma(t7))) && + (forall b Term :: {gamma(hash(b))} gamma(hash(b)) == hashB(gamma(b))) && + (forall b Term :: {gamma(kdf1(b))} gamma(kdf1(b)) == kdf1B(gamma(b))) && + (forall b Term :: {gamma(kdf2(b))} gamma(kdf2(b)) == kdf2B(gamma(b))) && + (forall b Term :: {gamma(kdf3(b))} gamma(kdf3(b)) == kdf3B(gamma(b))) && + (forall sk Term :: {gamma(createPk(sk))} gamma(createPk(sk)) == createPkB(gamma(sk))) && + (forall p, pk Term :: {gamma(encrypt(p,pk))} gamma(encrypt(p,pk)) == encryptB(gamma(p), gamma(pk))) && + (forall k, n, p, a Term :: {gamma(aead(k,n,p,a))} gamma(aead(k,n,p,a)) == aeadB(gamma(k), gamma(n), gamma(p), gamma(a))) && + (gamma(const1()) == const1B()) && + (forall l, r Term :: {gamma(exp(l,r))} gamma(exp(l,r)) == expB(gamma(l), gamma(r))) && + (forall l, r Term :: {gamma(mult(l,r))} gamma(mult(l,r)) == multB(gamma(l), gamma(r))) && + (forall s string :: {gamma(stringTerm(s))} gamma(stringTerm(s)) == stringB(s)) && + (forall l int :: {gamma(zeroString(l))} gamma(zeroString(l)) == zeroStringB(l)) && + (forall i uint64 :: {gamma(integer64(i))} gamma(integer64(i)) == integer64B(i)) && + (forall i uint32 :: {gamma(integer32(i))} gamma(integer32(i)) == integer32B(i)) && + (gamma(infoTerm()) == infoBytesB()) && + (gamma(prologueTerm()) == prologueBytesB()) && + (gamma(generator()) == generatorB()) && + (forall msg, sk Term :: {gamma(sign(msg, sk))} gamma(sign(msg, sk)) == signB(gamma(msg), gamma(sk))) + } +} diff --git a/ReusableVerificationLibrary/term/term.gobra b/ReusableVerificationLibrary/term/term.gobra new file mode 100644 index 0000000..60ebdfb --- /dev/null +++ b/ReusableVerificationLibrary/term/term.gobra @@ -0,0 +1,634 @@ +package term + +import "github.com/viperproject/ReusableProtocolVerificationLibrary/label" +import u "github.com/viperproject/ReusableProtocolVerificationLibrary/usage" + +/** + * `getTermHeight` is useful as a termination measure for (pure & impure) + * functions & methods that recurse on the children of a term e.g. to perform + * induction on terms. + * Note however that the axiomatisation of `getTermHeight` has to consider + * equational theories. E.g. (g^x)^y == g^(x * y) == g^(y * x) == (g^y)^x + * all have the same height. + */ + +type Term domain { + // type 0 + func random(Bytes, label.SecrecyLabel, u.Usage) Term + func getRandomBytes(Term) Bytes + func getRandomLabel(Term) label.SecrecyLabel + func getRandomUsage(Term) u.Usage + + // type 1 + func tuple2(Term, Term) Term + func getTupleElem(Term, int) Term + + // type 2 + func tuple3(Term, Term, Term) Term + + // type 3 + func tuple4(Term, Term, Term, Term) Term + + // type 4 + func tuple5(Term, Term, Term, Term, Term) Term + + // type 5 + func tuple7(Term, Term, Term, Term, Term, Term, Term) Term + + // type 6 + func hash(Term) Term + func getInput(Term) Term + + // type 7 + func kdf1(Term) Term + + // type 8 + func kdf2(Term) Term + + // type 9 + func kdf3(Term) Term + + // type 10 + /** returns the public key associated with a secret key */ + func createPk(Term) Term + func getSk(Term) Term + + // type 11 + /** plaintext, public key */ + func encrypt(Term, Term) Term + func getPlaintext(Term) Term + func getPk(Term) Term + + // type 12 + /** symmetric key, nonce, plaintext, auth text (i.e. text to be authenticated but not encrypted) */ + func aead(Term, Term, Term, Term) Term + func getAeadKey(Term) Term + func getAeadNonce(Term) Term + func getAuthtext(Term) Term + + // type 13 + func const1() Term + + // type 14 + func exp(Term, Term) Term + func getBase(Term) Term + func getExponent(Term) Term + + // type 15 + func mult(Term, Term) Term + + // type 16 + func stringTerm(string) Term + func getString(Term) string + + // type 17 + func zeroString(int) Term + + // type 18 + func integer64(uint64) Term + func getInt64(Term) uint64 + + // type 19 + func integer32(uint32) Term + func getInt32(Term) uint32 + + // type 20 + func infoTerm() Term + + // type 21 + func prologueTerm() Term + + // type 22 + func generator() Term + + // type 23 + func sign(Term, Term) Term + + // WARNING: adapt first axiom if another Term is added! + + func getTermType(Term) int + func getTermHeight(Term) int + func max2(int, int) int + func max3(int, int, int) int + func max4(int, int, int, int) int + func max5(int, int, int, int, int) int + func max7(int, int, int, int, int, int, int) int + + axiom { // every term belongs to a known type + forall term Term :: { getTermType(term) } 0 <= getTermType(term) && getTermType(term) <= 23 + } + + + axiom { // random is injective + forall b Bytes, l label.SecrecyLabel, usage u.Usage :: { random(b, l, usage) } getTermType(random(b, l, usage)) == 0 && + getTermHeight(random(b, l, usage)) == 0 && + getRandomBytes(random(b, l, usage)) == b && + getRandomLabel(random(b, l, usage)) == l && + getRandomUsage(random(b, l, usage)) == usage + } + + axiom { // tuple2 is injective + forall t1, t2 Term :: { tuple2(t1, t2) } getTermType(tuple2(t1, t2)) == 1 && + getTermHeight(tuple2(t1, t2)) == 1 + max2(getTermHeight(t1), getTermHeight(t2)) && + getTupleElem(tuple2(t1, t2), 0) == t1 && + getTupleElem(tuple2(t1, t2), 1) == t2 + } + axiom { // tuple2 implies its constructions + forall t Term :: { getTermType(t) } getTermType(t) == 1 ==> + t == tuple2(getTupleElem(t, 0), getTupleElem(t, 1)) + } + + axiom { // tuple3 is injective + forall t1, t2, t3 Term :: { tuple3(t1, t2, t3) } getTermType(tuple3(t1, t2, t3)) == 2 && + getTermHeight(tuple3(t1, t2, t3)) == 1 + max3(getTermHeight(t1), getTermHeight(t2), getTermHeight(t3)) && + getTupleElem(tuple3(t1, t2, t3), 0) == t1 && + getTupleElem(tuple3(t1, t2, t3), 1) == t2 && + getTupleElem(tuple3(t1, t2, t3), 2) == t3 + } + axiom { // tuple3 implies its constructions + forall t Term :: { getTermType(t) } getTermType(t) == 2 ==> + t == tuple3(getTupleElem(t, 0), getTupleElem(t, 1), getTupleElem(t, 2)) + } + + axiom { // tuple4 is injective + forall t1, t2, t3, t4 Term :: { tuple4(t1, t2, t3, t4) } getTermType(tuple4(t1, t2, t3, t4)) == 3 && + getTermHeight(tuple4(t1, t2, t3, t4)) == 1 + max4(getTermHeight(t1), getTermHeight(t2), getTermHeight(t3), getTermHeight(t4)) && + getTupleElem(tuple4(t1, t2, t3, t4), 0) == t1 && + getTupleElem(tuple4(t1, t2, t3, t4), 1) == t2 && + getTupleElem(tuple4(t1, t2, t3, t4), 2) == t3 && + getTupleElem(tuple4(t1, t2, t3, t4), 3) == t4 + } + axiom { // tuple4 implies its constructions + forall t Term :: { getTermType(t) } getTermType(t) == 3 ==> + t == tuple4(getTupleElem(t, 0), getTupleElem(t, 1), getTupleElem(t, 2), getTupleElem(t, 3)) + } + + axiom { // tuple5 is injective + forall t1, t2, t3, t4, t5 Term :: { tuple5(t1, t2, t3, t4, t5) } getTermType(tuple5(t1, t2, t3, t4, t5)) == 4 && + getTermHeight(tuple5(t1, t2, t3, t4, t5)) == 1 + max5(getTermHeight(t1), getTermHeight(t2), getTermHeight(t3), getTermHeight(t4), getTermHeight(t5)) && + getTupleElem(tuple5(t1, t2, t3, t4, t5), 0) == t1 && + getTupleElem(tuple5(t1, t2, t3, t4, t5), 1) == t2 && + getTupleElem(tuple5(t1, t2, t3, t4, t5), 2) == t3 && + getTupleElem(tuple5(t1, t2, t3, t4, t5), 3) == t4 && + getTupleElem(tuple5(t1, t2, t3, t4, t5), 4) == t5 + } + axiom { // tuple5 implies its constructions + forall t Term :: { getTermType(t) } getTermType(t) == 4 ==> + t == tuple5(getTupleElem(t, 0), getTupleElem(t, 1), getTupleElem(t, 2), getTupleElem(t, 3), getTupleElem(t, 4)) + } + + axiom { // tuple7 is injective + forall t1, t2, t3, t4, t5, t6, t7 Term :: { tuple7(t1, t2, t3, t4, t5, t6, t7) } getTermType(tuple7(t1, t2, t3, t4, t5, t6, t7)) == 5 && + getTermHeight(tuple7(t1, t2, t3, t4, t5, t6, t7)) == 1 + max7(getTermHeight(t1), getTermHeight(t2), getTermHeight(t3), getTermHeight(t4), getTermHeight(t5), getTermHeight(t6), getTermHeight(t7)) && + getTupleElem(tuple7(t1, t2, t3, t4, t5, t6, t7), 0) == t1 && + getTupleElem(tuple7(t1, t2, t3, t4, t5, t6, t7), 1) == t2 && + getTupleElem(tuple7(t1, t2, t3, t4, t5, t6, t7), 2) == t3 && + getTupleElem(tuple7(t1, t2, t3, t4, t5, t6, t7), 3) == t4 && + getTupleElem(tuple7(t1, t2, t3, t4, t5, t6, t7), 4) == t5 && + getTupleElem(tuple7(t1, t2, t3, t4, t5, t6, t7), 5) == t6 && + getTupleElem(tuple7(t1, t2, t3, t4, t5, t6, t7), 6) == t7 + } + axiom { // tuple7 implies its constructions + forall t Term :: { getTermType(t) } getTermType(t) == 5 ==> + t == tuple7(getTupleElem(t, 0), getTupleElem(t, 1), getTupleElem(t, 2), getTupleElem(t, 3), getTupleElem(t, 4), getTupleElem(t, 5), getTupleElem(t, 6)) + } + + axiom { // hash is injective + forall t Term :: { hash(t) } getTermType(hash(t)) == 6 && + getTermHeight(hash(t)) == 1 + getTermHeight(t) && + getInput(hash(t)) == t + } + axiom { // hash implies its constructions + forall t Term :: { getTermType(t) } getTermType(t) == 6 ==> + t == hash(getInput(t)) + } + + axiom { // kdf1 is injective + forall t Term :: { kdf1(t) } getTermType(kdf1(t)) == 7 && + getTermHeight(kdf1(t)) == 1 + getTermHeight(t) && + getInput(kdf1(t)) == t + } + axiom { // kdf1 implies its constructions + forall t Term :: { getTermType(t) } getTermType(t) == 7 ==> + t == kdf1(getInput(t)) + } + + axiom { // kdf2 is injective + forall t Term :: { kdf2(t) } getTermType(kdf2(t)) == 8 && + getTermHeight(kdf2(t)) == 1 + getTermHeight(t) && + getInput(kdf2(t)) == t + } + axiom { // kdf2 implies its constructions + forall t Term :: { getTermType(t) } getTermType(t) == 8 ==> + t == kdf2(getInput(t)) + } + + axiom { // kdf3 is injective + forall t Term :: { kdf3(t) } getTermType(kdf3(t)) == 9 && + getTermHeight(kdf3(t)) == 1 + getTermHeight(t) && + getInput(kdf3(t)) == t + } + axiom { // kdf3 implies its constructions + forall t Term :: { getTermType(t) } getTermType(t) == 9 ==> + t == kdf3(getInput(t)) + } + + axiom { // createPk is injective + forall sk Term :: { createPk(sk) } getTermType(createPk(sk)) == 10 && + getTermHeight(createPk(sk)) == 1 + getTermHeight(sk) && + getSk(createPk(sk)) == sk + } + axiom { // pk implies its construction + forall t Term :: { getTermType(t) } (getTermType(t) == 10) ==> t == createPk(getSk(t)) + } + + axiom { // encrypt is injective + forall plaintext Term, pk Term :: { encrypt(plaintext, pk) } getTermType(encrypt(plaintext, pk)) == 11 && + getTermHeight(encrypt(plaintext, pk)) == 1 + max2(getTermHeight(plaintext), getTermHeight(pk)) && + getPlaintext(encrypt(plaintext, pk)) == plaintext && + getPk(encrypt(plaintext, pk)) == pk + } + axiom { // encrypt implies its constructions + forall t Term :: { getTermType(t) } getTermType(t) == 11 ==> + t == encrypt(getPlaintext(t), getPk(t)) + } + + axiom { // aead is injective + forall key, nonce, plaintext, authtext Term :: { aead(key, nonce, plaintext, authtext) } getTermType(aead(key, nonce, plaintext, authtext)) == 12 && + getTermHeight(aead(key, nonce, plaintext, authtext)) == 1 + max4(getTermHeight(key), getTermHeight(nonce), getTermHeight(plaintext), getTermHeight(authtext)) && + getAeadKey(aead(key, nonce, plaintext, authtext)) == key && + getAeadNonce(aead(key, nonce, plaintext, authtext)) == nonce && + getPlaintext(aead(key, nonce, plaintext, authtext)) == plaintext && + getAuthtext(aead(key, nonce, plaintext, authtext)) == authtext + } + axiom { // aead implies its constructions + forall t Term :: { getTermType(t) } getTermType(t) == 12 ==> + t == aead(getAeadKey(t), getAeadNonce(t), getPlaintext(t), getAuthtext(t)) + } + + axiom { + getTermType(const1()) == 13 && + getTermHeight(const1()) == 0 + } + + axiom { // exp has type + forall t1, t2 Term :: { exp(t1, t2) } getTermType(exp(t1, t2)) == 14 && + getTermHeight(exp(t1, t2)) > max2(getTermHeight(t1), getTermHeight(t2)) // note that we use an inequality to prevent inconsistencies due to the DH equational theory + } + axiom { // exp is injective for a given base + forall t1, t2, t3 Term :: { exp(t1, t2), exp(t1, t3) } exp(t1, t2) == exp(t1, t3) ==> t2 == t3 + } + // exp is neither injective nor does it imply its construction as we can construct the same term by using exp and mult! + + axiom { // mult has type + forall t1, t2 Term :: { mult(t1, t2) } getTermType(mult(t1, t2)) == 15 && + getTermHeight(mult(t1, t2)) > max2(getTermHeight(t1), getTermHeight(t2)) // note that we use an inequality to prevent inconsistencies due to the DH equational theory + } + + // mult is commutative + axiom { + forall x, y Term :: { mult(x, y) } mult(x, y) == mult(y, x) + } + + // (x^y)^z == x^(y*z) + axiom { + forall x, y, z Term :: { exp(exp(x, y), z) } { exp(x, mult(y, z)) } exp(exp(x, y), z) == exp(x, mult(y, z)) + } + + // // x^1 == x + // axiom { + // forall x Term :: { exp(x, const1()) } exp(x, const1()) == x + // } + + // // mult is associative + // axiom { + // forall x, y, z Term :: { mult(mult(x, y), z) } { mult(x, mult(y, z)) } mult(mult(x, y), z) == mult(x, mult(y, z)) + // } + + // // x*1 == x + // axiom { + // forall x Term :: { mult(x, const1()) } mult(x, const1()) == x + // } + + // // x * inv(x) == 1 + // axiom { + // forall x Term :: { mult(x, inv(x)) } mult(x, inv(x)) == const1() + // } + + axiom { // stringTerm is injective + forall s string :: { stringTerm(s) } getTermType(stringTerm(s)) == 16 && + getTermHeight(stringTerm(s)) == 0 && + getString(stringTerm(s)) == s + } + + axiom { // zero string has type + forall l int :: { zeroString(l) } getTermType(zeroString(l)) == 17 && + getTermHeight(zeroString(l)) == 0 + } + + axiom { // integer64 is injective + forall value uint64 :: { integer64(value) } getTermType(integer64(value)) == 18 && + getTermHeight(integer64(value)) == 0 && + getInt64(integer64(value)) == value + } + axiom { // integer64 implies its construction + forall t Term :: { getTermType(t) } (getTermType(t) == 18) ==> t == integer64(getInt64(t)) + } + + axiom { // integer32 is injective + forall value uint32 :: { integer32(value) } getTermType(integer32(value)) == 19 && + getTermHeight(integer32(value)) == 0 && + getInt32(integer32(value)) == value + } + axiom { // integer32 implies its construction + forall t Term :: { getTermType(t) } (getTermType(t) == 19) ==> t == integer32(getInt32(t)) + } + + axiom { + getTermType(infoTerm()) == 20 && + getTermHeight(infoTerm()) == 0 + } + + axiom { + getTermType(prologueTerm()) == 21 && + getTermHeight(prologueTerm()) == 0 + } + + axiom { + getTermType(generator()) == 22 && + getTermHeight(generator()) == 0 + } + axiom { // generator implies its construction + forall t Term :: { getTermType(t) } (getTermType(t) == 22) ==> t == generator() + } + + axiom { // sign is injective + forall plaintext Term, sk Term :: { sign(plaintext, sk) } getTermType(sign(plaintext, sk)) == 23 && + getTermHeight(sign(plaintext, sk)) == 1 + max2(getTermHeight(plaintext), getTermHeight(sk)) && + getPlaintext(sign(plaintext, sk)) == plaintext && + getSk(sign(plaintext, sk)) == sk + } + axiom { // sign implies its constructions + forall t Term :: { getTermType(t) } getTermType(t) == 23 ==> + t == sign(getPlaintext(t), getSk(t)) + } + + // axioms for getTermHeight and the max functions: + axiom { + forall t Term :: { getTermHeight(t) } 0 <= getTermHeight(t) + } + axiom { + forall t1, t2 int :: { max2(t1, t2) } max2(t1, t2) == (t1 >= t2 ? t1 : t2) + } + axiom { + forall t1, t2, t3 int :: { max3(t1, t2, t3) } max3(t1, t2, t3) == max2(t1, max2(t2, t3)) + } + axiom { + forall t1, t2, t3, t4 int :: { max4(t1, t2, t3, t4) } max4(t1, t2, t3, t4) == max2(max2(t1, t2), max2(t3, t4)) + } + axiom { + forall t1, t2, t3, t4, t5 int :: { max5(t1, t2, t3, t4, t5) } max5(t1, t2, t3, t4, t5) == max2(max2(t1, t2), max3(t3, t4, t5)) + } + axiom { + forall t1, t2, t3, t4, t5, t6, t7 int :: { max7(t1, t2, t3, t4, t5, t6, t7) } max7(t1, t2, t3, t4, t5, t6, t7) == max2(max3(t1, t2, t3), max4(t4, t5, t6, t7)) + } +} + +ghost +decreases +pure func (term Term) IsRandom() bool { + return getTermType(term) == 0 +} + +ghost +decreases +pure func (term Term) IsTuple() bool { + return term.IsTuple2() || term.IsTuple3() || term.IsTuple4() || term.IsTuple5() || /*term.IsTuple6() ||*/ term.IsTuple7() +} + +ghost +decreases +pure func (term Term) GetTupleArity() int { + return term.IsTuple2() ? 2 : + term.IsTuple3() ? 3 : + term.IsTuple4() ? 4 : + term.IsTuple5() ? 5 : + // term.IsTuple6() ? 6: + 7 +} + +ghost +decreases +pure func (term Term) IsTuple2() bool { + return getTermType(term) == 1 +} + +ghost +decreases +pure func (term Term) IsTuple3() bool { + return getTermType(term) == 2 +} + +ghost +decreases +pure func (term Term) IsTuple4() bool { + return getTermType(term) == 3 +} + +ghost +decreases +pure func (term Term) IsTuple5() bool { + return getTermType(term) == 4 +} + +ghost +decreases +pure func (term Term) IsTuple7() bool { + return getTermType(term) == 5 +} + +ghost +decreases +pure func (term Term) IsHash() bool { + return getTermType(term) == 6 +} + +ghost +decreases +pure func (term Term) IsKdf() bool { + return term.IsKdf1() || term.IsKdf2() || term.IsKdf3() +} + +ghost +decreases +pure func (term Term) IsKdf1() bool { + return getTermType(term) == 7 +} + +ghost +decreases +pure func (term Term) IsKdf2() bool { + return getTermType(term) == 8 +} + +ghost +decreases +pure func (term Term) IsKdf3() bool { + return getTermType(term) == 9 +} + +ghost +decreases +pure func (term Term) IsPk() bool { + return getTermType(term) == 10 +} + +ghost +decreases +pure func (term Term) IsEncrypt() bool { + return getTermType(term) == 11 +} + +ghost +decreases +pure func (term Term) IsAead() bool { + return getTermType(term) == 12 +} + +ghost +decreases +pure func (term Term) IsConst1() bool { + return getTermType(term) == 13 +} + +ghost +decreases +pure func (term Term) IsExp() bool { + return getTermType(term) == 14 +} + +ghost +decreases +pure func (term Term) IsMult() bool { + return getTermType(term) == 15 +} + +ghost +decreases +pure func (term Term) IsString() bool { + return getTermType(term) == 16 +} + +ghost +decreases +pure func (term Term) IsZeroString() bool { + return getTermType(term) == 17 +} + +ghost +decreases +pure func (term Term) IsInteger64() bool { + return getTermType(term) == 18 +} + +ghost +decreases +pure func (term Term) IsInteger32() bool { + return getTermType(term) == 19 +} + +ghost +decreases +pure func (term Term) IsInfo() bool { + return getTermType(term) == 20 +} + +ghost +decreases +pure func (term Term) IsPrologue() bool { + return getTermType(term) == 21 +} + +ghost +decreases +pure func (term Term) IsGenerator() bool { + return getTermType(term) == 22 +} + +ghost +decreases +pure func (term Term) IsSignature() bool { + return getTermType(term) == 23 +} + + +ghost +decreases +pure func decrypt(ciphertext, sk Term) option[Term] { + return ciphertext.IsEncrypt() ? + (getPk(ciphertext) == createPk(sk) ? + some(getPlaintext(ciphertext)) : + // sk does not match: + none[Term]) : + none[Term] // not a ciphertext +} + +requires decrypt(ciphertext, sk) == some(plaintext) +ensures ciphertext == encrypt(plaintext, createPk(sk)) +func pkeDecLemma(sk Term, ciphertext Term, plaintext Term) { + // no body needed +} + +requires sk1 != sk2 +requires ciphertext == encrypt(plaintext, createPk(sk1)) +ensures decrypt(ciphertext, sk1) == some(plaintext) +ensures decrypt(ciphertext, sk2) == none[Term] +func keyMismatchResultsInFailure(sk1, sk2, ciphertext, plaintext Term) { + // no body needed +} + +ghost +decreases +pure func AeadDecrypt(ciphertext, key, nonce, ad Term) option[Term] { + return ciphertext.IsAead() ? + ((getAeadKey(ciphertext) == key && getAeadNonce(ciphertext) == nonce && getAuthtext(ciphertext) == ad) ? + some(getPlaintext(ciphertext)) : + // key, nonce or additional data does not match: + none[Term]) : + none[Term] // not an AEAD ciphertext +} + +ghost +decreases +pure func AeadVerify(ciphertext, key, nonce, authtext Term) bool { + return ciphertext.IsAead() ? + (getAeadKey(ciphertext) == key && + getAeadNonce(ciphertext) == nonce && + getAuthtext(ciphertext) == authtext) : + false // not an AEAD ciphertext +} + +ghost +decreases +requires ciphertext.IsAead() +pure func AeadExtract(ciphertext Term) Term { + return getAuthtext(ciphertext) +} + +ghost +decreases +pure func Open(signature, pk Term) option[Term] { + return signature.IsSignature() ? + (createPk(getSk(signature)) == pk ? + some(getPlaintext(signature)) : + // pk does not match + none[Term]) : + none[Term] // no a signature +} + +ghost +decreases +requires signature.IsSignature() +pure func SignExtract(signature Term) Term { + return getPlaintext(signature) +} diff --git a/ReusableVerificationLibrary/trace/entry-helpers.gobra b/ReusableVerificationLibrary/trace/entry-helpers.gobra new file mode 100644 index 0000000..495fbfc --- /dev/null +++ b/ReusableVerificationLibrary/trace/entry-helpers.gobra @@ -0,0 +1,362 @@ +package trace + +import . "github.com/viperproject/ReusableProtocolVerificationLibrary/event" +import "github.com/viperproject/ReusableProtocolVerificationLibrary/label" +import p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" +import tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" +import u "github.com/viperproject/ReusableProtocolVerificationLibrary/usage" + +ghost +decreases _ // TODO: show that this function terminates because we are only calling ADT deconstructors +ensures 0 <= res +pure func (entry TraceEntry) traceLen() (res int) { + return entry.isRoot() ? 1 : 1 + (getPrev(entry)).traceLen() +} + +ghost +decreases t2.traceLen() +requires t1.isSuffix(t2) +ensures t1.traceLen() <= t2.traceLen() +func (t1 TraceEntry) traceLenMontonic(t2 TraceEntry) { + if (t1 != t2) { + t1.traceLenMontonic(getPrev(t2)) + } +} + +ghost +decreases t1.traceLen() +ensures t0 == t1 ==> res +// the following postcondition optimizes a very common use case (which requires the postcondition above) +// without the postcondition, one first needs to call `getPrev(t).isSuffix(getPrev(t))` in order +// that Gobra is able to deduce that `getPrev(t).isSuffix(t)` holds +ensures (!t1.isRoot() && t0 == getPrev(t1)) ==> res +pure func (t0 TraceEntry) isSuffix(t1 TraceEntry) (res bool) { + return (t0 == t1) ? true : + (t1.isRoot()) ? false : + t0.isSuffix(getPrev(t1)) +} + +ghost +decreases t3.traceLen() +requires t1.isSuffix(t2) && t2.isSuffix(t3) +ensures t1.isSuffix(t3) +func (t1 TraceEntry) isSuffixTransitive(t2, t3 TraceEntry) { + if (t2 != t3) { + t1.isSuffixTransitive(t2, getPrev(t3)) + } +} + +// several helper functions to simplify expressing properties about the trace: + +/** returns true iff specified event occurs exactly a the current trace entry */ +ghost +decreases +pure func (entry TraceEntry) isEventAt(principal p.Principal, ev Event) bool { + return entry.isEvent() && + getPrincipal(entry) == principal && + getEvent(entry) == ev +} + +/** returns true iff specified event occurs somewhere on the trace */ +ghost +decreases entry.traceLen() +pure func (entry TraceEntry) eventOccurs(principal p.Principal, ev Event) bool { + return entry.isRoot() ? false : + entry.isEventAt(principal, ev) ? true : + (getPrev(entry)).eventOccurs(principal, ev) +} + +ghost +decreases t2.traceLen() +requires t1.isSuffix(t2) +requires t1.eventOccurs(principal, ev) +ensures t2.eventOccurs(principal, ev) +func (t1 TraceEntry) eventOccursMonotonic(t2 TraceEntry, principal p.Principal, ev Event) { + if (t1 != t2) { + t1.eventOccursMonotonic(getPrev(t2), principal, ev) + } +} + +ghost +decreases entry.traceLen() +pure func (entry TraceEntry) eventOccursAt(principal p.Principal, ev Event, index int) bool { + return entry.isRoot() ? false : + entry.isEventAt(principal, ev) && index == entry.traceLen() - 1? true : + (getPrev(entry)).eventOccursAt(principal, ev, index) +} + +ghost +decreases t2.traceLen() +requires t1.isSuffix(t2) +requires t1.eventOccursAt(principal, ev, index) +ensures t2.eventOccursAt(principal, ev, index) +func (t1 TraceEntry) eventOccursAtMonotonic(t2 TraceEntry, principal p.Principal, ev Event, index int) { + if (t1 != t2) { + t1.eventOccursAtMonotonic(getPrev(t2), principal, ev, index) + } +} + +/** returns the trace length at the point when the specified event occurred */ +ghost +decreases entry.traceLen() +requires entry.eventOccurs(principal, ev) +pure func (entry TraceEntry) eventOccursAtTime(principal p.Principal, ev Event) int { + return entry.isEventAt(principal, ev) ? entry.traceLen() : + (getPrev(entry)).eventOccursAtTime(principal, ev) +} + +ghost +decreases entry.traceLen() +requires entry.eventOccursAt(principal, ev, idx) +ensures entry.eventOccurs(principal, ev) +func (entry TraceEntry) eventOccursAtLemma(principal p.Principal, ev Event, idx int) { + if !entry.isEventAt(principal, ev) { + getPrev(entry).eventOccursAtLemma(principal, ev, idx) + } +} + +ghost +decreases entry.traceLen() +requires entry.eventOccurs(principal, ev) +ensures entry.eventOccursAt(principal, ev, res) +ensures res == entry.eventOccursAtTime(principal, ev) - 1 +func (entry TraceEntry) eventOccursAtTimeLemma(principal p.Principal, ev Event) (res int) { + if entry.isEventAt(principal, ev) { + res = entry.traceLen() - 1 + } else { + res = getPrev(entry).eventOccursAtTimeLemma(principal, ev) + } +} + +ghost +decreases entry.traceLen() +requires entry.eventOccurs(principal, ev) +ensures res.isSuffix(entry) +ensures res.isEventAt(principal, ev) +/** returns the TraceEntry at which the event is at */ +pure func (entry TraceEntry) eventOccursWitness(principal p.Principal, ev Event) (res TraceEntry) { + return entry.isEventAt(principal, ev) ? entry : + (getPrev(entry)).eventOccursWitness(principal, ev) +} + +ghost +decreases t2.traceLen() +requires t1.isSuffix(t2) +requires t1.eventOccurs(principal, ev) +ensures t2.eventOccurs(principal, ev) +ensures t1.eventOccursWitness(principal, ev).isSuffix(t2.eventOccursWitness(principal, ev)) +func (t1 TraceEntry) eventOccursWitnessMonotonic(t2 TraceEntry, principal p.Principal, ev Event) { + if t1 != t2 { + if t2.isEventAt(principal, ev) { + t1Witness := t1.eventOccursWitness(principal, ev) + t1Witness.isSuffixTransitive(t1, t2) + } else { + t1.eventOccursWitnessMonotonic(getPrev(t2), principal, ev) + } + } +} + +/** returns true iff specified message occurs exactly a the current trace entry */ +ghost +decreases +pure func (entry TraceEntry) isMessageAt(sender, receiver p.Principal, payload tm.Term) bool { + return entry.isMessage() && + getSender(entry) == sender && + getReceiver(entry) == receiver && + getPayload(entry) == payload +} + +/** returns true iff specified message occurs somewhere on the trace */ +ghost +decreases entry.traceLen() +pure func (entry TraceEntry) messageOccurs(sender, receiver p.Principal, payload tm.Term) bool { + return entry.isRoot() ? false : + entry.isMessageAt(sender, receiver, payload) ? true : + (getPrev(entry)).messageOccurs(sender, receiver, payload) +} + +ghost +decreases t2.traceLen() +requires t1.isSuffix(t2) +requires t1.messageOccurs(sender, receiver, payload) +ensures t2.messageOccurs(sender, receiver, payload) +func (t1 TraceEntry) messageOccursMonotonic(t2 TraceEntry, sender, receiver p.Principal, payload tm.Term) { + if (t1 != t2) { + t1.messageOccursMonotonic(getPrev(t2), sender, receiver, payload) + } +} + +ghost +decreases +pure func (entry TraceEntry) isMessageDroppedAt(sender, receiver p.Principal, payload tm.Term) bool { + return entry.isDropMessage() && + getSender(entry) == sender && + getReceiver(entry) == receiver && + getPayload(entry) == payload +} + +/** returns true iff specified nonce occurs exactly a the current trace entry */ +ghost +decreases +pure func (entry TraceEntry) isNonceAt(nonce tm.Term) bool { + return entry.isNonce() && getNonce(entry) == nonce +} + +/** returns true iff specified nonce occurs somewhere on the trace */ +ghost +decreases entry.traceLen() +pure func (entry TraceEntry) OnlyNonceOccurs(nonce tm.Term) bool { + return entry.isRoot() ? false : + entry.isNonceAt(nonce) ? true : + (getPrev(entry)).OnlyNonceOccurs(nonce) +} + +ghost +decreases entry.traceLen() +pure func (entry TraceEntry) nonceOccurs(nonce tm.Term, l label.SecrecyLabel, usage u.Usage) (res bool) { + return entry.OnlyNonceOccurs(nonce) && + tm.getRandomLabel(nonce) == l && + tm.getRandomUsage(nonce) == usage +} + +ghost +decreases t2.traceLen() +requires t1.isSuffix(t2) +requires t1.OnlyNonceOccurs(nonce) +ensures t2.OnlyNonceOccurs(nonce) +func (t1 TraceEntry) OnlyNonceOccursMonotonic(t2 TraceEntry, nonce tm.Term) { + if (t1 != t2) { + t1.OnlyNonceOccursMonotonic(getPrev(t2), nonce) + } +} + +ghost +decreases t2.traceLen() +requires t1.isSuffix(t2) +requires t1.nonceOccurs(nonce, l, usage) +ensures t2.nonceOccurs(nonce, l, usage) +func (t1 TraceEntry) nonceOccursMonotonic(t2 TraceEntry, nonce tm.Term, l label.SecrecyLabel, usage u.Usage) { + t1.OnlyNonceOccursMonotonic(t2, nonce) +} + +/** + * does not consider actions performed by attacker but returns just the initial set + * of public bytes. Values published by the attacker can be obtained by calling `getTermsMadePublic` + */ +ghost +decreases entry.traceLen() +pure func (entry TraceEntry) getPublicTerms() set[tm.Term] { + return entry.isRoot() ? + getPublicTerms(entry) : + (getPrev(entry)).getPublicTerms() +} + +ghost +decreases t2.traceLen() +requires t1.isSuffix(t2) +ensures t1.getPublicTerms() == t2.getPublicTerms() +func (t1 TraceEntry) getPublicTermsMonotonic(t2 TraceEntry) { + if t1 != t2 { + t1.getPublicTermsMonotonic(getPrev(t2)) + } +} + +ghost +decreases entry.traceLen() +pure func (entry TraceEntry) getTermsMadePublic() set[tm.Term] { + return (entry.isRoot() ? set[tm.Term]{} : (getPrev(entry)).getTermsMadePublic()) union ( + entry.isPublic() ? set[tm.Term]{getPayload(entry)} : set[tm.Term]{}) +} + +ghost +decreases t2.traceLen() +requires t1.isSuffix(t2) +ensures t1.getTermsMadePublic() subset t2.getTermsMadePublic() +func (t1 TraceEntry) getTermsMadePublicMonotonic(t2 TraceEntry) { + if t1 != t2 { + t1.getTermsMadePublicMonotonic(getPrev(t2)) + } +} + +ghost +decreases entry.traceLen() +pure func (entry TraceEntry) getCorruptPrincipals() set[p.Principal] { + return (entry.isRoot() ? set[p.Principal]{} : (getPrev(entry)).getCorruptPrincipals()) union ( + entry.isCorrupt() ? set[p.Principal]{(getId(entry)).getPrincipal()} : set[p.Principal]{}) +} + +ghost +decreases entry.traceLen() +pure func (entry TraceEntry) getCorruptIds() set[p.Id] { + return (entry.isRoot() ? set[p.Id]{} : (getPrev(entry)).getCorruptIds()) union ( + entry.isCorrupt() ? set[p.Id]{getId(entry)} : set[p.Id]{}) +} + +ghost +decreases t2.traceLen() +requires t1.isSuffix(t2) +ensures t1.getCorruptIds() subset t2.getCorruptIds() +func (t1 TraceEntry) getCorruptIdsMonotonic(t2 TraceEntry) { + if (t1 != t2) { + t1.getCorruptIdsMonotonic(getPrev(t2)) + } +} + +ghost +decreases entry.traceLen() +pure func (entry TraceEntry) getMessagePayloads() set[tm.Term] { + return (entry.isRoot() ? set[tm.Term]{} : (getPrev(entry)).getMessagePayloads()) union ( + entry.isMessage() ? set[tm.Term]{getPayload(entry)} : set[tm.Term]{}) +} + +ghost +decreases t2.traceLen() +requires t1.isSuffix(t2) +ensures t1.getMessagePayloads() subset t2.getMessagePayloads() +func (t1 TraceEntry) getMessagePayloadsMonotonic(t2 TraceEntry) { + if t1 != t2 { + t1.getMessagePayloadsMonotonic(getPrev(t2)) + } +} + +ghost +decreases entry.traceLen() +requires msg in entry.getMessagePayloads() +ensures entry.messageOccurs(sender, receiver, msg) +func (entry TraceEntry) getMsgSenderReceiver(msg tm.Term) (sender, receiver p.Principal) { + if entry.isMessage() && getPayload(entry) == msg { + sender = getSender(entry) + receiver = getReceiver(entry) + } else { + sender, receiver = (getPrev(entry)).getMsgSenderReceiver(msg) + } +} + +ghost +decreases entry.traceLen() +ensures forall msg tm.Term :: { msg in res } msg in res ==> entry.messageOccurs(sender, receiver, msg) +pure func (entry TraceEntry) getMessagePayloadsForCommChannel(sender, receiver p.Principal) (res set[tm.Term]) { + return (entry.isRoot() ? set[tm.Term]{} : (getPrev(entry)).getMessagePayloadsForCommChannel(sender, receiver)) union ( + (entry.isMessage() && getSender(entry) == sender && getReceiver(entry) == receiver) ? set[tm.Term]{getPayload(entry)} : set[tm.Term]{}) +} + + +ghost +decreases +/** returns true iff at least one id in `ids` has been corrupted, i.e. is in `corruptIds` */ +pure func containsCorruptId(corruptIds set[p.Id], ids set[p.Id]) bool { + // return len(corruptIds intersection ids) > 0 + return exists corruptedId p.Id :: { containsId(ids, corruptedId) } corruptedId in corruptIds && containsId(ids, corruptedId) +} + +ghost +decreases +/** +* returns true if there is an id in `ids` that covers `corruptedId`. +* the intuition is that e.g. a long-term private key (with label `readers(set{owner})`) +* can be published if any session of that participant (i.e. `owner`) has been +* corrupted. This relation is expressed by `Covers` because `owner` covers all its sessions +*/ +pure func containsId(ids set[p.Id], corruptedId p.Id) bool { + return exists id p.Id :: { id.Covers(corruptedId) } id in ids && id.Covers(corruptedId) +} diff --git a/ReusableVerificationLibrary/trace/entry.gobra b/ReusableVerificationLibrary/trace/entry.gobra new file mode 100644 index 0000000..88bba93 --- /dev/null +++ b/ReusableVerificationLibrary/trace/entry.gobra @@ -0,0 +1,133 @@ +package trace + +import . "github.com/viperproject/ReusableProtocolVerificationLibrary/event" +import "github.com/viperproject/ReusableProtocolVerificationLibrary/label" +import p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" +import tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" +import u "github.com/viperproject/ReusableProtocolVerificationLibrary/usage" + +type TraceEntry domain { + // constructors + // type 0 + func makeRoot(set[tm.Term]) TraceEntry + // type 1 + func makeEvent(TraceEntry, p.Principal, Event) TraceEntry + // type 2 + func makeMessage(TraceEntry, p.Principal, p.Principal, tm.Term) TraceEntry + // type 3 + func makeDropMessage(TraceEntry, p.Principal, p.Principal, tm.Term) TraceEntry + // type 4 + func makeNonce(TraceEntry, tm.Term) TraceEntry + // type 5 + func makePublic(TraceEntry, tm.Term) TraceEntry + // type 6 + func makeCorrupt(TraceEntry, p.Id) TraceEntry + + // WARNING: adapt first axiom if another TraceEntry is added! + + // deconstructors + func getPrev(TraceEntry) TraceEntry + func getPublicTerms(TraceEntry) set[tm.Term] + func getPrincipal(TraceEntry) p.Principal + func getEvent(TraceEntry) Event + func getSender(TraceEntry) p.Principal + func getReceiver(TraceEntry) p.Principal + func getPayload(TraceEntry) tm.Term + func getNonce(TraceEntry) tm.Term + func getId(TraceEntry) p.Id + + // each entry has a unique type + func getType(TraceEntry) int + + axiom { // all entries have a type + forall t TraceEntry :: { getType(t) } 0 <= getType(t) && getType(t) <= 6 + } + + axiom { // root entry is injective + forall terms set[tm.Term] :: { makeRoot(terms) } getType(makeRoot(terms)) == 0 && + getPublicTerms(makeRoot(terms)) == terms + } + + axiom { // event entry is injective + forall prev TraceEntry, principal p.Principal, ev Event :: { makeEvent(prev, principal, ev) } getType(makeEvent(prev, principal, ev)) == 1 && + getPrev(makeEvent(prev, principal, ev)) == prev && + getPrincipal(makeEvent(prev, principal, ev)) == principal && + getEvent(makeEvent(prev, principal, ev)) == ev + } + + axiom { // message entry is injective + forall prev TraceEntry, sender, receiver p.Principal, payload tm.Term :: { makeMessage(prev, sender, receiver, payload) } getType(makeMessage(prev, sender, receiver, payload)) == 2 && + getPrev(makeMessage(prev, sender, receiver, payload)) == prev && + getSender(makeMessage(prev, sender, receiver, payload)) == sender && + getReceiver(makeMessage(prev, sender, receiver, payload)) == receiver && + getPayload(makeMessage(prev, sender, receiver, payload)) == payload + } + + axiom { // drop message entry is injective + forall prev TraceEntry, sender, receiver p.Principal, payload tm.Term :: { makeDropMessage(prev, sender, receiver, payload) } getType(makeDropMessage(prev, sender, receiver, payload)) == 3 && + getPrev(makeDropMessage(prev, sender, receiver, payload)) == prev && + getSender(makeDropMessage(prev, sender, receiver, payload)) == sender && + getReceiver(makeDropMessage(prev, sender, receiver, payload)) == receiver && + getPayload(makeDropMessage(prev, sender, receiver, payload)) == payload + } + + axiom { // nonce entry is injective + forall prev TraceEntry, nonce tm.Term :: { makeNonce(prev, nonce) } getType(makeNonce(prev, nonce)) == 4 && + getPrev(makeNonce(prev, nonce)) == prev && + getNonce(makeNonce(prev, nonce)) == nonce + } + + axiom { // public entry is injective + forall prev TraceEntry, publicTerm tm.Term :: { makePublic(prev, publicTerm) } getType(makePublic(prev, publicTerm)) == 5 && + getPrev(makePublic(prev, publicTerm)) == prev && + getPayload(makePublic(prev, publicTerm)) == publicTerm + } + + axiom { // corrupt entry is injective + forall prev TraceEntry, id p.Id :: { makeCorrupt(prev, id) } getType(makeCorrupt(prev, id)) == 6 && + getPrev(makeCorrupt(prev, id)) == prev && + getId(makeCorrupt(prev, id)) == id + } +} + +ghost +decreases +pure func (entry TraceEntry) isRoot() (res bool) { + return getType(entry) == 0 +} + +ghost +decreases +pure func (entry TraceEntry) isEvent() (res bool) { + return getType(entry) == 1 +} + +ghost +decreases +pure func (entry TraceEntry) isMessage() bool { + return getType(entry) == 2 +} + +ghost +decreases +pure func (entry TraceEntry) isDropMessage() bool { + return getType(entry) == 3 +} + +ghost +decreases +pure func (entry TraceEntry) isNonce() bool { + return getType(entry) == 4 +} + +ghost +decreases +pure func (entry TraceEntry) isPublic() bool { + return getType(entry) == 5 +} + +ghost +decreases +pure func (entry TraceEntry) isCorrupt() bool { + return getType(entry) == 6 +} diff --git a/ReusableVerificationLibrary/traceinvariant/trace-invariant.gobra b/ReusableVerificationLibrary/traceinvariant/trace-invariant.gobra new file mode 100644 index 0000000..de48ad2 --- /dev/null +++ b/ReusableVerificationLibrary/traceinvariant/trace-invariant.gobra @@ -0,0 +1,180 @@ +package traceinvariant + +import . "github.com/viperproject/ReusableProtocolVerificationLibrary/event" +import label "github.com/viperproject/ReusableProtocolVerificationLibrary/label" +import . "github.com/viperproject/ReusableProtocolVerificationLibrary/labeling" +import p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" +import tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" +import . "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" +import u "github.com/viperproject/ReusableProtocolVerificationLibrary/usage" +import . "github.com/viperproject/ReusableProtocolVerificationLibrary/usagecontext" + +type TraceContext interface { + ghost + decreases + /** used to express (pure) properties about fields of TraceContext implementations */ + pure Props() bool + + pred eventInv(principal p.Principal, ev Event, prev TraceEntry) + + ghost + decreases + /** + * Specifies consistency conditions for an event. These conditions + * have to be implied by `pureEventInv` + */ + pure EventConsistency(ev Event) bool + + ghost + decreases + pure pureEventInv(principal p.Principal, ev Event, prev TraceEntry) bool + + ghost + decreases + requires pureEventInv(principal, ev, prev) + ensures EventConsistency(ev) + eventInvImpliesConsistency(principal p.Principal, ev Event, prev TraceEntry) + + ghost + decreases + /** + * expresses whether an event type does only occur once with + * certain parameters + */ + pure IsUnique(typ EventType) bool + + ghost + decreases + requires EventConsistency(ev) + requires IsUnique(ev.typ) + /** + * returns the witness from which event uniqueness can be derived. + * This typically is a nonce stored as an event parameter + */ + pure UniquenessWitness(ev Event) tm.Term + + ghost + decreases + requires Props() + requires IsUnique(ev.typ) + requires eventInv(principal, ev, prev) + ensures EventConsistency(ev) + ensures nonce == UniquenessWitness(ev) + ensures GetLabelingContext(GetUsageContext()).NonceForEventIsUnique(nonce, ev.typ) + ensures GetLabelingContext(GetUsageContext()).NonceForEventIsUnique(nonce, ev.typ) --* eventInv(principal, ev, prev) + isUniqueImpliesUniqueResource(principal p.Principal, ev Event, prev TraceEntry) (nonce tm.Term) + + ghost + decreases + requires noPerm < p && p <= writePerm + requires acc(eventInv(principal, ev, prev), p) + ensures acc(eventInv(principal, ev, prev), p) + ensures pureEventInv(principal, ev, prev) + getPureEventInv(principal p.Principal, ev Event, prev TraceEntry, p perm) + + ghost + decreases + requires t1.isSuffix(t2) + requires eventInv(principal, ev, t1) + ensures eventInv(principal, ev, t2) + eventInvMonotonic(principal p.Principal, ev Event, t1 TraceEntry, t2 TraceEntry) + + ghost + decreases + requires t1.isSuffix(t2) + requires pureEventInv(principal, ev, t1) + ensures pureEventInv(principal, ev, t2) + pureEventInvMonotonic(principal p.Principal, ev Event, t1 TraceEntry, t2 TraceEntry) + + ghost + decreases + requires Props() + ensures res != nil + pure GetUsageContext() (res UsageContext) +} + +ghost +decreases +requires ctx != nil && ctx.Props() +ensures res.Props() +pure func GetLabeling(ctx TraceContext) (res LabelingContext) { + return GetLabelingContext(ctx.GetUsageContext()) +} + +// because Go & Gobra do not allow receivers of interface type, `ctx` becomes the first argument: +pred validTrace(ctx TraceContext, t TraceEntry) { + ctx != nil && ctx.Props() && + // invariant holds recursively: + (!t.isRoot() ==> validTrace(ctx, getPrev(t))) && + + (t.isRoot() ==> publicInv(ctx, getPublicTerms(t), t)) && + (t.isEvent() ==> ctx.eventInv(getPrincipal(t), getEvent(t), getPrev(t))) && + (t.isMessage() ==> messageInv(ctx, getSender(t), getReceiver(t), getPayload(t), getPrev(t))) && + (t.isDropMessage() ==> messageInv(ctx, getSender(t), getReceiver(t), getPayload(t), getPrev(t))) && + (t.isNonce() ==> randInv(ctx, getNonce(t), getPrev(t))) && + (t.isPublic() ==> madePublicInv(ctx, getPayload(t), getPrev(t))) +} + +ghost +decreases +requires ctx != nil && ctx.Props() +requires t.isRoot() +pure func publicInv(ctx TraceContext, publicTerms set[tm.Term], t TraceEntry) bool { + return forall publicTerm tm.Term :: { publicTerm in publicTerms } publicTerm in publicTerms ==> GetLabeling(ctx).IsPublishable(t, publicTerm) +} + +ghost +decreases +requires ctx != nil && ctx.Props() +pure func messageInv(ctx TraceContext, sender, receiver p.Principal, payload tm.Term, prev TraceEntry) bool { + return GetLabeling(ctx).IsPublishable(prev, payload) +} + +ghost +decreases +requires ctx != nil && ctx.Props() +requires t1.isSuffix(t2) +requires messageInv(ctx, sender, receiver, payload, t1) +ensures messageInv(ctx, sender, receiver, payload, t2) +func messageInvTransitive(ctx TraceContext, sender, receiver p.Principal, payload tm.Term, t1, t2 TraceEntry) { + GetLabeling(ctx).IsPublishableMonotonic(t1, t2, payload) +} + +pred randInv(ctx TraceContext, nonce tm.Term, prev TraceEntry) { + ctx != nil && ctx.Props() && + pureRandInv(ctx, nonce, prev) && + GetLabeling(ctx).NonceIsUnique(nonce) +} + +ghost +decreases +pure func pureRandInv(ctx TraceContext, nonce tm.Term, prev TraceEntry) bool { + return nonce.IsRandom() +} + +ghost +decreases +requires ctx != nil && ctx.Props() +requires t1.isSuffix(t2) +requires pureRandInv(ctx, nonce, t1) +ensures pureRandInv(ctx, nonce, t2) +func pureRandInvTransitive(ctx TraceContext, nonce tm.Term, t1, t2 TraceEntry) { + // no body needed +} + +ghost +decreases +requires ctx != nil && ctx.Props() +pure func madePublicInv(ctx TraceContext, term tm.Term, prev TraceEntry) bool { + return GetLabeling(ctx).IsPublishable(prev, term) +} + +ghost +decreases +requires ctx != nil && ctx.Props() +requires t1.isSuffix(t2) +requires madePublicInv(ctx, term, t1) +ensures madePublicInv(ctx, term, t2) +func madePublicInvTransitive(ctx TraceContext, term tm.Term, t1, t2 TraceEntry) { + GetLabeling(ctx).IsPublishableMonotonic(t1, t2, term) +} diff --git a/ReusableVerificationLibrary/tracemanager/entry-lemmas.gobra b/ReusableVerificationLibrary/tracemanager/entry-lemmas.gobra new file mode 100644 index 0000000..c8d04b0 --- /dev/null +++ b/ReusableVerificationLibrary/tracemanager/entry-lemmas.gobra @@ -0,0 +1,96 @@ +package tracemanager + +import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" +import tri "github.com/viperproject/ReusableProtocolVerificationLibrary/traceinvariant" + + +type EntrySearcher struct { + ctx tri.TraceContext + entry tr.TraceEntry +} +EntrySearcher implements TraceSearcher + +ghost +decreases +pure func (es EntrySearcher) Props() bool { + return es.ctx != nil && es.ctx.Props() +} + +ghost +decreases +requires es.Props() +ensures res != nil && res.Props() +pure func (es EntrySearcher) Ctx() (res tri.TraceContext) { + return es.ctx +} + +ghost +decreases +pure func (es EntrySearcher) Matches(entry tr.TraceEntry) bool { + return entry == es.entry +} + +ghost +decreases +requires entry.isSuffix(snapshot) +requires es.Matches(entry) +pure func (es EntrySearcher) MatchProperties(snapshot, entry tr.TraceEntry) bool { + return true +} + +ghost +decreases +pure func (es EntrySearcher) Occurs(entry tr.TraceEntry) bool { + return es.entry.isSuffix(entry) +} + +ghost +decreases +requires es.Props() +pure func (es EntrySearcher) PureEntryInv(entry tr.TraceEntry) bool { + return es.entry.isEvent() ==> es.ctx.pureEventInv(tr.getPrincipal(es.entry), tr.getEvent(es.entry), tr.getPrev(entry)) +} + +ghost +decreases +requires es.Matches(entry) +ensures es.MatchProperties(entry, entry) +func (es EntrySearcher) MatchPropertiesReflexive(snapshot, entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires entry.isSuffix(snapshot) +requires es.Matches(entry) +requires !es.Matches(snapshot) +requires es.MatchProperties(tr.getPrev(snapshot), entry) +ensures es.MatchProperties(snapshot, entry) +func (es EntrySearcher) MatchPropertiesTransitive(snapshot, entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires es.Occurs(entry) +ensures (!entry.isRoot() && es.Occurs(tr.getPrev(entry))) || es.Matches(entry) +func (es EntrySearcher) OccursImpliesAnEventualMatch(entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires noPerm < p && p <= writePerm +requires es.Props() +requires es.Matches(entry) +requires acc(tri.validTrace(es.Ctx(), entry), p) +ensures es.PureEntryInv(entry) +ensures acc(tri.validTrace(es.Ctx(), entry), p) +func (es EntrySearcher) ExtractPureEntryInv(entry tr.TraceEntry, p perm) { + unfold acc(tri.validTrace(es.Ctx(), entry), p) + if es.entry.isEvent() { + es.Ctx().getPureEventInv(tr.getPrincipal(es.entry), tr.getEvent(es.entry), tr.getPrev(entry), p) + } + fold acc(tri.validTrace(es.Ctx(), entry), p) +} +// end of EntrySearcher's implementation diff --git a/ReusableVerificationLibrary/tracemanager/event-lemmas.gobra b/ReusableVerificationLibrary/tracemanager/event-lemmas.gobra new file mode 100644 index 0000000..8bbe8c8 --- /dev/null +++ b/ReusableVerificationLibrary/tracemanager/event-lemmas.gobra @@ -0,0 +1,334 @@ +package tracemanager + +import ev "github.com/viperproject/ReusableProtocolVerificationLibrary/event" +import p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" +import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" +import tri "github.com/viperproject/ReusableProtocolVerificationLibrary/traceinvariant" + + +/** search for latest matching event (irrespective of a particular trace index) */ +type EventSearcher struct { + ctx tri.TraceContext + principal p.Principal + event ev.Event +} +EventSearcher implements TraceSearcher + +ghost +decreases +pure func (es EventSearcher) Props() bool { + return es.ctx != nil && es.ctx.Props() +} + +ghost +decreases +requires es.Props() +ensures res != nil && res.Props() +pure func (es EventSearcher) Ctx() (res tri.TraceContext) { + return es.ctx +} + +ghost +decreases +pure func (es EventSearcher) Matches(entry tr.TraceEntry) bool { + return entry.isEventAt(es.principal, es.event) +} + +ghost +decreases +requires entry.isSuffix(snapshot) +requires es.Matches(entry) +pure func (es EventSearcher) MatchProperties(snapshot tr.TraceEntry, entry tr.TraceEntry) bool { + return snapshot.eventOccurs(es.principal, es.event) && + entry == snapshot.eventOccursWitness(es.principal, es.event) +} + +ghost +decreases +pure func (es EventSearcher) Occurs(entry tr.TraceEntry) bool { + return entry.eventOccurs(es.principal, es.event) +} + +ghost +decreases +requires es.Props() +pure func (es EventSearcher) PureEntryInv(entry tr.TraceEntry) bool { + return es.Ctx().pureEventInv(es.principal, es.event, tr.getPrev(entry)) +} + +ghost +decreases +requires es.Matches(entry) +ensures es.MatchProperties(entry, entry) +func (es EventSearcher) MatchPropertiesReflexive(snapshot, entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires entry.isSuffix(snapshot) +requires es.Matches(entry) +requires !es.Matches(snapshot) +requires es.MatchProperties(tr.getPrev(snapshot), entry) +ensures es.MatchProperties(snapshot, entry) +func (es EventSearcher) MatchPropertiesTransitive(snapshot, entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires es.Occurs(entry) +ensures (!entry.isRoot() && es.Occurs(tr.getPrev(entry))) || es.Matches(entry) +func (es EventSearcher) OccursImpliesAnEventualMatch(entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires noPerm < p && p <= writePerm +requires es.Props() +requires es.Matches(entry) +requires acc(tri.validTrace(es.Ctx(), entry), p) +ensures es.PureEntryInv(entry) +ensures acc(tri.validTrace(es.Ctx(), entry), p) +func (es EventSearcher) ExtractPureEntryInv(entry tr.TraceEntry, p perm) { + unfold acc(tri.validTrace(es.Ctx(), entry), p) + (es.Ctx()).getPureEventInv(es.principal, es.event, tr.getPrev(entry), p) + fold acc(tri.validTrace(es.Ctx(), entry), p) +} +// end of EventSearcher's implementation + + +/** search for matching event taking the trace index into account */ +type EventIdxSearcher struct { + ctx tri.TraceContext + principal p.Principal + event ev.Event + idx int +} +EventIdxSearcher implements TraceSearcher + +ghost +decreases +pure func (es EventIdxSearcher) Props() bool { + return es.ctx != nil && es.ctx.Props() +} + +ghost +decreases +requires es.Props() +ensures res != nil && res.Props() +pure func (es EventIdxSearcher) Ctx() (res tri.TraceContext) { + return es.ctx +} + +ghost +decreases +pure func (es EventIdxSearcher) Matches(entry tr.TraceEntry) bool { + return entry.isEventAt(es.principal, es.event) && es.idx == entry.traceLen() - 1 +} + +ghost +decreases +requires entry.isSuffix(snapshot) +requires es.Matches(entry) +pure func (es EventIdxSearcher) MatchProperties(snapshot tr.TraceEntry, entry tr.TraceEntry) bool { + return snapshot.eventOccursAt(es.principal, es.event, es.idx) && + es.idx == entry.traceLen() - 1 +} + +ghost +decreases +pure func (es EventIdxSearcher) Occurs(entry tr.TraceEntry) bool { + return entry.eventOccursAt(es.principal, es.event, es.idx) +} + +ghost +decreases +requires es.Props() +pure func (es EventIdxSearcher) PureEntryInv(entry tr.TraceEntry) bool { + return es.Ctx().pureEventInv(es.principal, es.event, tr.getPrev(entry)) +} + +ghost +decreases +requires es.Matches(entry) +ensures es.MatchProperties(entry, entry) +func (es EventIdxSearcher) MatchPropertiesReflexive(snapshot, entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires entry.isSuffix(snapshot) +requires es.Matches(entry) +requires !es.Matches(snapshot) +requires es.MatchProperties(tr.getPrev(snapshot), entry) +ensures es.MatchProperties(snapshot, entry) +func (es EventIdxSearcher) MatchPropertiesTransitive(snapshot, entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires es.Occurs(entry) +ensures (!entry.isRoot() && es.Occurs(tr.getPrev(entry))) || es.Matches(entry) +func (es EventIdxSearcher) OccursImpliesAnEventualMatch(entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires noPerm < p && p <= writePerm +requires es.Props() +requires es.Matches(entry) +requires acc(tri.validTrace(es.Ctx(), entry), p) +ensures es.PureEntryInv(entry) +ensures acc(tri.validTrace(es.Ctx(), entry), p) +func (es EventIdxSearcher) ExtractPureEntryInv(entry tr.TraceEntry, p perm) { + unfold acc(tri.validTrace(es.Ctx(), entry), p) + (es.Ctx()).getPureEventInv(es.principal, es.event, tr.getPrev(entry), p) + fold acc(tri.validTrace(es.Ctx(), entry), p) +} +// end of EventIdxSearcher's implementation + + +ghost +decreases +requires m.Mem(ctx, owner) +requires snap.isSuffix(m.Snapshot(ctx, owner)) +requires snap.eventOccurs(principal, event) +ensures m.Mem(ctx, owner) +ensures prev.isSuffix(snap) && prev.isEvent() +ensures prev.isSuffix(m.Snapshot(ctx, owner)) +ensures prev.isEventAt(principal, event) +ensures snap.eventOccurs(principal, event) +ensures prev == snap.eventOccursWitness(principal, event) +ensures m.Ctx(ctx, owner).pureEventInv(principal, event, tr.getPrev(prev)) +ensures old(m.Snapshot(ctx, owner)) == m.Snapshot(ctx, owner) +ensures m.ImmutableState(ctx, owner) == old(m.ImmutableState(ctx, owner)) +func (m *TraceManager) EventOccursImpliesEventInvWithSnap(ctx tri.TraceContext, owner Client, snap tr.TraceEntry, principal p.Principal, event ev.Event) (prev tr.TraceEntry) { + searcher := EventSearcher{ ctx, principal, event } + // the following assert stmt is necessary to derive that `ctx != nil`: + assert unfolding m.Mem(ctx, owner) in true + prev = m.findEntryWithPureInvWithSnap(searcher, owner, snap) + prev.isSuffixTransitive(snap, m.Snapshot(ctx, owner)) +} + +ghost +decreases +requires m.Mem(ctx, owner) +requires m.Snapshot(ctx, owner).eventOccursAt(principal1, event1, idx1) +requires m.Snapshot(ctx, owner).eventOccursAt(principal2, event2, idx2) +requires m.Ctx(ctx, owner).IsUnique(event1.typ) +requires event1.typ == event2.typ +requires ctx.EventConsistency(event1) && ctx.EventConsistency(event2) +requires ctx.UniquenessWitness(event1) == ctx.UniquenessWitness(event2) +ensures m.Mem(ctx, owner) +ensures principal1 == principal2 +ensures event1 == event2 +ensures idx1 == idx2 +ensures m.ImmutableState(ctx, owner) == old(m.ImmutableState(ctx, owner)) +ensures m.Snapshot(ctx, owner) == old(m.Snapshot(ctx, owner)) +// this lemma is part of the manager as it requires the validTrace predicate +func (m *TraceManager) UniqueEventsAreUniqueAt(ctx tri.TraceContext, owner Client, principal1, principal2 p.Principal, event1, event2 ev.Event, idx1, idx2 int) { + // to obtain the validTrace predicate instance, we have to aquire the lock and apply monotonicity: + inv := ManagerInv{ ctx } + lastSeenTrace := m.Snapshot(ctx, owner) + unfold m.Mem(ctx, owner) + trace := m.mutex.Lock(inv, owner) + unfold inv.CurrentValueInv(trace) + lastSeenTrace.eventOccursAtMonotonic(trace, principal1, event1, idx1) + lastSeenTrace.eventOccursAtMonotonic(trace, principal2, event2, idx2) + s1, s2 := m.findEvents(ctx, trace, principal1, principal2, event1, event2, idx1, idx2) + if (s1 != s2) { + // derive contradiction + nonce1 := ctx.isUniqueImpliesUniqueResource(principal1, event1, tr.getPrev(s1)) + nonce2 := ctx.isUniqueImpliesUniqueResource(principal2, event2, tr.getPrev(s2)) + tri.GetLabeling(ctx).NonceForEventContradiction(nonce1, event1.typ) + } + // the following assertion would hold: + // assert s1 == s2 + apply ctx.eventInv(principal1, event1, tr.getPrev(s1)) --* tri.validTrace(ctx, trace) + fold inv.CurrentValueInv(trace) + // there is no need to update the snapshot + m.mutex.UnlockWithSnapshot(inv, owner, trace, trace, lastSeenTrace) + fold m.Mem(ctx, owner) +} + +ghost +decreases +requires m.Mem(ctx, owner) +requires suffix.isSuffix(m.Snapshot(ctx, owner)) +requires suffix.eventOccurs(principal, event) +requires m.Ctx(ctx, owner).IsUnique(event.typ) +ensures m.Mem(ctx, owner) +ensures m.Snapshot(ctx, owner).eventOccurs(principal, event) +ensures suffix.eventOccursWitness(principal, event) == m.Snapshot(ctx, owner).eventOccursWitness(principal, event) +ensures old(m.Snapshot(ctx, owner)) == m.Snapshot(ctx, owner) +ensures m.ImmutableState(ctx, owner) == old(m.ImmutableState(ctx, owner)) +func (m *TraceManager) UniqueEventsOccurOnlyOnce(ctx tri.TraceContext, owner Client, suffix tr.TraceEntry, principal p.Principal, event ev.Event) { + inv := ManagerInv{ ctx } + lastSeenTrace := m.Snapshot(ctx, owner) + suffix.eventOccursMonotonic(lastSeenTrace, principal, event) + + suffixWitness := suffix.eventOccursWitness(principal, event) + suffixWitness.isSuffixTransitive(suffix, lastSeenTrace) + traceWitness := lastSeenTrace.eventOccursWitness(principal, event) + + unfold m.Mem(ctx, owner) + trace := m.mutex.Lock(inv, owner) + unfold inv.CurrentValueInv(trace) + searcher1 := EntrySearcher{ ctx, suffixWitness } + searcher2 := EntrySearcher{ ctx, traceWitness } + suffixWitness.isSuffixTransitive(lastSeenTrace, trace) + traceWitness.isSuffixTransitive(lastSeenTrace, trace) + s1, s2 := m.findEntriesWithInv(searcher1, searcher2, trace) + if s1 == s2 { + apply localValidTrace(ctx, s1) --* tri.validTrace(ctx, trace) + } else { + // derive contradiction + unfold localValidTrace(ctx, s1) + unfold localValidTrace(ctx, s2) + nonce1 := ctx.isUniqueImpliesUniqueResource(principal, event, tr.getPrev(s1)) + nonce2 := ctx.isUniqueImpliesUniqueResource(principal, event, tr.getPrev(s2)) + tri.GetLabeling(ctx).NonceForEventContradiction(nonce1, event.typ) + } + fold inv.CurrentValueInv(trace) + // there is no need to update the snapshot + m.mutex.UnlockWithSnapshot(inv, owner, trace, trace, lastSeenTrace) + fold m.Mem(ctx, owner) +} + +ghost +decreases +requires ctx != nil && isComparable(ctx) && ctx.Props() +requires tri.validTrace(ctx, validTraceEntry) +requires validTraceEntry.eventOccursAt(principal1, event1, idx1) +requires validTraceEntry.eventOccursAt(principal2, event2, idx2) +ensures s1.isEventAt(principal1, event1) +ensures s2.isEventAt(principal2, event2) +ensures s1.traceLen() - 1 == idx1 +ensures s2.traceLen() - 1 == idx2 +ensures s1 == s2 ==> + principal1 == principal2 && + event1 == event2 && + ctx.eventInv(principal1, event1, tr.getPrev(s1)) && + (ctx.eventInv(principal1, event1, tr.getPrev(s1)) --* tri.validTrace(ctx, validTraceEntry)) +ensures s1 != s2 ==> + ctx.eventInv(principal1, event1, tr.getPrev(s1)) && + ctx.eventInv(principal2, event2, tr.getPrev(s2)) +func (m *TraceManager) findEvents(ctx tri.TraceContext, validTraceEntry tr.TraceEntry, principal1, principal2 p.Principal, event1, event2 ev.Event, idx1, idx2 int) (s1, s2 tr.TraceEntry) { + searcher1 := EventIdxSearcher{ ctx, principal1, event1, idx1 } + searcher2 := EventIdxSearcher{ ctx, principal2, event2, idx2 } + s1, s2 = m.findEntriesWithInv(searcher1, searcher2, validTraceEntry) + unfold localValidTrace(ctx, s1) + if s1 == s2 { + package ctx.eventInv(principal1, event1, tr.getPrev(s1)) --* tri.validTrace(ctx, validTraceEntry) { + fold localValidTrace(ctx, s1) + apply localValidTrace(ctx, s1) --* tri.validTrace(ctx, validTraceEntry) + } + } else { + unfold localValidTrace(ctx, s2) + } +} diff --git a/ReusableVerificationLibrary/tracemanager/manager.gobra b/ReusableVerificationLibrary/tracemanager/manager.gobra new file mode 100644 index 0000000..83c88de --- /dev/null +++ b/ReusableVerificationLibrary/tracemanager/manager.gobra @@ -0,0 +1,339 @@ +package tracemanager + +// uses concurrent datastructure to establish the invariant that any older trace is a suffix of the current one + +import ev "github.com/viperproject/ReusableProtocolVerificationLibrary/event" +import "github.com/viperproject/ReusableProtocolVerificationLibrary/label" +import p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" +import tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" +import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" +import tri "github.com/viperproject/ReusableProtocolVerificationLibrary/traceinvariant" +import ts "github.com/viperproject/ReusableProtocolVerificationLibrary/concurrentdatastructure" + + +type TraceManager struct { + nClients int + mutex *ts.ClientHistoryMutex +} + +type Client = p.Id +// currently necessary because Gobra does apparently not support a dot expression as map key type +type MapKey = p.Id + +decreases +requires ctx != nil && isComparable(ctx) && ctx.Props() +requires len(clients) > 0 +requires tri.validTrace(ctx, root) +requires noPerm < p && p <= writePerm +requires forall j int :: { clients[j] } 0 <= j && j < len(clients) ==> acc(&clients[j], p) +requires forall j, k int :: { clients[j], clients[k] } 0 <= j && j < k && k < len(clients) ==> clients[j] != clients[k] +ensures forall j int :: { clients[j] } 0 <= j && j < len(clients) ==> acc(&clients[j], p) +ensures forall j, k int :: { clients[j], clients[k] } 0 <= j && j < k && k < len(clients) ==> clients[j] != clients[k] +ensures acc(ms) +ensures forall j int :: { clients[j] } 0 <= j && j < len(clients) ==> ms[clients[j]].Mem(ctx, clients[j]) +ensures forall j, k int :: { clients[j], clients[k] } 0 <= j && j < len(clients) && 0 <= k && k < len(clients) ==> + ms[clients[j]].ImmutableState(ctx, clients[j]) == ms[clients[k]].ImmutableState(ctx, clients[k]) +func NewTraceManager(ctx tri.TraceContext, clients []Client, root tr.TraceEntry, ghost p perm) (ms map[MapKey]*TraceManager) { + ms = make(map[MapKey]*TraceManager) + + // forall quantification complains if `inv` is of type ManagerInv instead of ClientHistoryInv: + var inv ts.ClientHistoryInv + inv = ManagerInv{ ctx } + mutex := ts.NewClientHistoryMutex() + fold inv.CurrentValueInv(root) + mutex.SetInv(inv, clients, root, p/2) + + // we allocate a slice of trace managers such that we know that the pointers are pairwise distinct: + managers := make([]TraceManager, len(clients)) + + invariant 0 <= i && i <= len(clients) + invariant forall j int :: { clients[j] } 0 <= j && j < len(clients) ==> acc(&clients[j], p) + invariant forall j, k int :: { clients[j], clients[k] } 0 <= j && j < k && k < len(clients) ==> clients[j] != clients[k] + invariant forall j int :: { clients[j] } i <= j && j < len(clients) ==> mutex.ClientHistoryMutexState(inv, clients[j]) + invariant len(clients) == len(managers) + invariant forall j int :: { managers[j] } i <= j && j < len(managers) ==> acc(&managers[j]) + invariant forall j, k int :: { managers[j], managers[k] } 0 <= j && j < k && k < len(managers) ==> &managers[j] != &managers[k] + invariant acc(ms) + invariant forall j int :: { clients[j], managers[j] } 0 <= j && j < i ==> ms[clients[j]] == &managers[j] + invariant forall j int :: { managers[j] } 0 <= j && j < i ==> (&managers[j]).Mem(ctx, clients[j]) + invariant forall j int :: { clients[j] } 0 <= j && j < i ==> + ((ms[clients[j]].ImmutableState(ctx, clients[j])).mutex == mutex) + decreases len(clients) - i + for i := 0; i < len(clients); i++ { + client := clients[i] + m := &managers[i] + m.nClients = len(clients) + m.mutex = mutex + ms[client] = m + fold m.Mem(ctx, client) + } +} + +pred (m *TraceManager) Mem(ctx tri.TraceContext, owner Client) { + acc(m) && 0 < m.nClients && + ctx != nil && isComparable(ctx) && ctx.Props() && + m.mutex.ClientHistoryMutexState(ManagerInv{ ctx }, owner) +} + +ghost +decreases +requires acc(m.Mem(ctx, owner), _) +ensures ctx != nil && ctx.Props() +// indirection to learn that `ctx != nil` +pure func (m *TraceManager) Ctx(ctx tri.TraceContext, owner Client) tri.TraceContext { + return unfolding acc(m.Mem(ctx, owner), _) in ctx +} + +ghost +decreases +requires acc(m.Mem(ctx, owner), _) +pure func (m *TraceManager) Snapshot(ctx tri.TraceContext, owner Client) tr.TraceEntry { + return unfolding acc(m.Mem(ctx, owner), _) in m.mutex.LastSeenValue(ManagerInv{ ctx }, owner) +} + +// abstract over all memory that remains unchanged after manager initialization +// TODO should be ghost +type ImmutableState struct { + mutex *ts.ClientHistoryMutex + // clients do not need to be included as they follow from pointer equality on the mutex and the `ClientsAreIdentical` lemma +} + +ghost +decreases +requires acc(m.Mem(ctx, owner), _) +pure func (m *TraceManager) ImmutableState(ctx tri.TraceContext, owner Client) ImmutableState { + return unfolding acc(m.Mem(ctx, owner), _) in ImmutableState{ m.mutex } +} + +ghost +decreases +requires m.Mem(ctx, owner) +ensures m.Mem(ctx, owner) +ensures trace == m.Snapshot(ctx, owner) +ensures old(m.Snapshot(ctx, owner)).isSuffix(trace) +ensures m.ImmutableState(ctx, owner) == old(m.ImmutableState(ctx, owner)) +// this method simply updates the local snapshot to the global trace and returns +// the updated snapshot for convenience reasons +func (m *TraceManager) Sync(ctx tri.TraceContext, owner Client) (trace tr.TraceEntry) { + inv := ManagerInv{ ctx } + lastSeenTrace := m.Snapshot(ctx, owner) + unfold m.Mem(ctx, owner) + trace = m.mutex.Lock(inv, owner) + // we have obtained the global trace `trace` and immediately release the lock again: + m.mutex.Unlock(inv, owner, trace, trace) + fold m.Mem(ctx, owner) +} + +ghost +decreases +requires destManager.Mem(ctx, destOwner) +requires acc(origManager.Mem(ctx, origOwner), 1/8) +requires destManager.ImmutableState(ctx, destOwner) == origManager.ImmutableState(ctx, origOwner) +ensures destManager.Mem(ctx, destOwner) +ensures acc(origManager.Mem(ctx, origOwner), 1/8) +ensures (destManager.Snapshot(ctx, destOwner)) == (origManager.Snapshot(ctx, origOwner)) +ensures destManager.ImmutableState(ctx, destOwner) == old(destManager.ImmutableState(ctx, destOwner)) +func (destManager *TraceManager) SetSnapshot(origManager *TraceManager, ctx tri.TraceContext, destOwner, origOwner Client) { + inv := ManagerInv{ ctx } + lastSeenTrace := destManager.Snapshot(ctx, destOwner) + unfold destManager.Mem(ctx, destOwner) + trace := destManager.mutex.Lock(inv, destOwner) + // leave trace unchanged and only update snapshot + // because destManager and origManager share the same immutable state, we can derive + // that both owners have to be included in the snapshots map: + unfold acc(origManager.Mem(ctx, origOwner), 1/8) + destManager.mutex.ClientsAreIdenticalLocked1(inv, destOwner, origOwner, trace) + unfold destManager.mutex.ClientHistoryMutexStateLocked(inv, destOwner, trace) + // unfold the origManager's clientHistoryMutexState predicate to derive that we are talking about the right snapshot + unfold acc(origManager.mutex.ClientHistoryMutexState(inv, origOwner), 1/8) + snapshot := *(destManager.mutex.snapshots)[origOwner] + + // the following assert stmt is not necessary but would hold: + // assert snapshot == *(origManager.mutex.snapshots)[origOwner] + fold acc(origManager.mutex.ClientHistoryMutexState(inv, origOwner), 1/8) + fold acc(origManager.Mem(ctx, origOwner), 1/8) + + *(destManager.mutex.snapshots)[destOwner] = snapshot + fold destManager.mutex.ClientHistoryMutexStateLocked(inv, destOwner, trace) + destManager.mutex.UnlockWithSnapshot(inv, destOwner, trace, trace, snapshot) + fold destManager.Mem(ctx, destOwner) +} + +ghost +decreases +requires m.Mem(ctx, owner) +requires ctx.eventInv(owner.getPrincipal(), event, m.Snapshot(ctx, owner)) +ensures m.Mem(ctx, owner) +// note that we do not specify here that the event occurs at the most recent trace entry because this property +// would not hold since the trace might have been extended by another participant or the attacker after +// releasing the lock. However, the event occurs at the most recent trace entry from this participant's +// snapshot of the trace. +ensures (m.Snapshot(ctx, owner)).isEventAt(owner.getPrincipal(), event) +ensures old(m.Snapshot(ctx, owner)).isSuffix(m.Snapshot(ctx, owner)) +ensures m.ImmutableState(ctx, owner) == old(m.ImmutableState(ctx, owner)) +func (m *TraceManager) LogEvent(ctx tri.TraceContext, owner Client, event ev.Event) { + inv := ManagerInv{ ctx } + lastSeenTrace := m.Snapshot(ctx, owner) + unfold m.Mem(ctx, owner) + prevTrace := m.mutex.Lock(inv, owner) + unfold inv.CurrentValueInv(prevTrace) + // extend trace + trace := tr.makeEvent(prevTrace, owner.getPrincipal(), event) + ctx.eventInvMonotonic(owner.getPrincipal(), event, lastSeenTrace, prevTrace) + fold tri.validTrace(ctx, trace) + fold inv.CurrentValueInv(trace) + m.mutex.Unlock(inv, owner, prevTrace, trace) + fold m.Mem(ctx, owner) +} + +ghost +decreases +requires m.Mem(ctx, owner) +requires tri.messageInv(m.Ctx(ctx, owner), sender, receiver, message, m.Snapshot(ctx, owner)) +ensures m.Mem(ctx, owner) +ensures (m.Snapshot(ctx, owner)).isMessageAt(sender, receiver, message) +ensures old(m.Snapshot(ctx, owner)).isSuffix(m.Snapshot(ctx, owner)) +ensures m.ImmutableState(ctx, owner) == old(m.ImmutableState(ctx, owner)) +func (m *TraceManager) LogSend(ctx tri.TraceContext, owner Client, sender, receiver p.Principal, message tm.Term) { + inv := ManagerInv{ ctx } + lastSeenTrace := m.Snapshot(ctx, owner) + unfold m.Mem(ctx, owner) + prevTrace := m.mutex.Lock(inv, owner) + unfold inv.CurrentValueInv(prevTrace) + // extend trace + trace := tr.makeMessage(prevTrace, sender, receiver, message) + tri.messageInvTransitive(ctx, sender, receiver, message, lastSeenTrace, prevTrace) + fold tri.validTrace(ctx, trace) + fold inv.CurrentValueInv(trace) + m.mutex.Unlock(inv, owner, prevTrace, trace) + fold m.Mem(ctx, owner) +} + +ghost +decreases +requires m.Mem(ctx, owner) +requires tri.messageInv(m.Ctx(ctx, owner), sender, receiver, message, m.Snapshot(ctx, owner)) +ensures m.Mem(ctx, owner) +ensures (m.Snapshot(ctx, owner)).isMessageDroppedAt(sender, receiver, message) +ensures old(m.Snapshot(ctx, owner)).isSuffix(m.Snapshot(ctx, owner)) +ensures m.ImmutableState(ctx, owner) == old(m.ImmutableState(ctx, owner)) +func (m *TraceManager) LogMsgDrop(ctx tri.TraceContext, owner Client, sender, receiver p.Principal, message tm.Term) { + inv := ManagerInv{ ctx } + lastSeenTrace := m.Snapshot(ctx, owner) + unfold m.Mem(ctx, owner) + prevTrace := m.mutex.Lock(inv, owner) + unfold inv.CurrentValueInv(prevTrace) + // extend trace + trace := tr.makeDropMessage(prevTrace, sender, receiver, message) + tri.messageInvTransitive(ctx, sender, receiver, message, lastSeenTrace, prevTrace) + fold tri.validTrace(ctx, trace) + fold inv.CurrentValueInv(trace) + m.mutex.Unlock(inv, owner, prevTrace, trace) + fold m.Mem(ctx, owner) +} + +ghost +decreases +requires m.Mem(ctx, owner) +requires tri.madePublicInv(m.Ctx(ctx, owner), term, m.Snapshot(ctx, owner)) +ensures m.Mem(ctx, owner) +ensures (m.Snapshot(ctx, owner)).isPublic() && tr.getPayload(m.Snapshot(ctx, owner)) == term +ensures old(m.Snapshot(ctx, owner)).isSuffix(m.Snapshot(ctx, owner)) +ensures m.ImmutableState(ctx, owner) == old(m.ImmutableState(ctx, owner)) +func (m *TraceManager) LogPublish(ctx tri.TraceContext, owner Client, term tm.Term) { + inv := ManagerInv{ ctx } + lastSeenTrace := m.Snapshot(ctx, owner) + unfold m.Mem(ctx, owner) + prevTrace := m.mutex.Lock(inv, owner) + unfold inv.CurrentValueInv(prevTrace) + // extend trace + trace := tr.makePublic(prevTrace, term) + tri.madePublicInvTransitive(ctx, term, lastSeenTrace, prevTrace) + fold tri.validTrace(ctx, trace) + fold inv.CurrentValueInv(trace) + m.mutex.Unlock(inv, owner, prevTrace, trace) + fold m.Mem(ctx, owner) +} + +ghost +decreases +requires m.Mem(ctx, owner) +ensures m.Mem(ctx, owner) +ensures id in m.Snapshot(ctx, owner).getCorruptIds() +ensures tr.containsCorruptId(m.Snapshot(ctx, owner).getCorruptIds(), set[p.Id]{ id }) +ensures old(m.Snapshot(ctx, owner)).isSuffix(m.Snapshot(ctx, owner)) +ensures m.ImmutableState(ctx, owner) == old(m.ImmutableState(ctx, owner)) +func (m *TraceManager) LogCorruption(ctx tri.TraceContext, owner Client, id p.Id) { + inv := ManagerInv{ ctx } + lastSeenTrace := m.Snapshot(ctx, owner) + unfold m.Mem(ctx, owner) + prevTrace := m.mutex.Lock(inv, owner) + unfold inv.CurrentValueInv(prevTrace) + // extend trace + trace := tr.makeCorrupt(prevTrace, id) + fold tri.validTrace(ctx, trace) + fold inv.CurrentValueInv(trace) + m.mutex.Unlock(inv, owner, prevTrace, trace) + fold m.Mem(ctx, owner) + id.CoversReflexive() + assert tr.containsId(set[p.Id]{ id }, id) +} + +ghost +decreases +requires m.Mem(ctx, owner) +requires nonce.IsRandom() +requires tri.GetLabeling(m.Ctx(ctx, owner)).NonceIsUnique(nonce) +// the following precondition specifies that `owner` can read the nonce +// i.e. this enforces for upper library layers that an owner cannot create nonces +// or keys that it cannot read itself +requires tri.GetLabeling(ctx).CanFlow(m.Snapshot(ctx, owner), tm.getRandomLabel(nonce), label.Readers(set[p.Id]{ owner.IsSessionThread() ? p.sessionId(p.getIdPrincipal(owner), p.getIdSession(owner)) : owner })) +ensures m.Mem(ctx, owner) +ensures m.Snapshot(ctx, owner).isNonceAt(nonce) +ensures old(m.Snapshot(ctx, owner)).isSuffix(m.Snapshot(ctx, owner)) +ensures m.ImmutableState(ctx, owner) == old(m.ImmutableState(ctx, owner)) +func (m *TraceManager) LogNonce(ctx tri.TraceContext, owner Client, nonce tm.Term) { + inv := ManagerInv{ ctx } + lastSeenTrace := m.Snapshot(ctx, owner) + unfold m.Mem(ctx, owner) + prevTrace := m.mutex.Lock(inv, owner) + unfold inv.CurrentValueInv(prevTrace) + // extend trace + trace := tr.makeNonce(prevTrace, nonce) + fold tri.randInv(ctx, nonce, prevTrace) + fold tri.validTrace(ctx, trace) + fold inv.CurrentValueInv(trace) + m.mutex.Unlock(inv, owner, prevTrace, trace) + fold m.Mem(ctx, owner) +} + +type ManagerInv struct { + ctx tri.TraceContext +} +// the following clause is technically not necessary: +ManagerInv implements ts.ClientHistoryInv + +pred (m ManagerInv) CurrentValueInv(t tr.TraceEntry) { + tri.validTrace(m.ctx, t) +} + +ghost +decreases +pure func (m ManagerInv) TwoStepValueInv(oldValue, currentValue tr.TraceEntry) bool { + return oldValue.isSuffix(currentValue) +} + +ghost +decreases +ensures m.TwoStepValueInv(value, value) +func (m ManagerInv) TwoStepValueInvReflexive(value tr.TraceEntry) { + value.isSuffix(value) +} + +ghost +decreases +requires m.TwoStepValueInv(val1, val2) && m.TwoStepValueInv(val2, val3) +ensures m.TwoStepValueInv(val1, val3) +func (m ManagerInv) TwoStepValueInvTransitive(val1, val2, val3 tr.TraceEntry) { + val1.isSuffixTransitive(val2, val3) +} diff --git a/ReusableVerificationLibrary/tracemanager/message-lemmas.gobra b/ReusableVerificationLibrary/tracemanager/message-lemmas.gobra new file mode 100644 index 0000000..060a18e --- /dev/null +++ b/ReusableVerificationLibrary/tracemanager/message-lemmas.gobra @@ -0,0 +1,117 @@ +package tracemanager + +import p "github.com/viperproject/ReusableProtocolVerificationLibrary/principal" +import tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" +import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" +import tri "github.com/viperproject/ReusableProtocolVerificationLibrary/traceinvariant" + + +type MessageSearcher struct { + ctx tri.TraceContext + sender p.Principal + receiver p.Principal + payload tm.Term +} +MessageSearcher implements TraceSearcher + +ghost +decreases +pure func (ms MessageSearcher) Props() bool { + return ms.ctx != nil && ms.ctx.Props() +} + +ghost +decreases +requires ms.Props() +ensures res != nil && res.Props() +pure func (ms MessageSearcher) Ctx() (res tri.TraceContext) { + return ms.ctx +} + +ghost +decreases +pure func (ms MessageSearcher) Matches(entry tr.TraceEntry) bool { + return entry.isMessageAt(ms.sender, ms.receiver, ms.payload) +} + +ghost +decreases +requires entry.isSuffix(snapshot) +requires ms.Matches(entry) +pure func (ms MessageSearcher) MatchProperties(snapshot, entry tr.TraceEntry) bool { + return true +} + +ghost +decreases +pure func (ms MessageSearcher) Occurs(entry tr.TraceEntry) bool { + return entry.messageOccurs(ms.sender, ms.receiver, ms.payload) +} + +ghost +decreases +requires ms.Props() +pure func (ms MessageSearcher) PureEntryInv(entry tr.TraceEntry) bool { + return tri.messageInv(ms.ctx, ms.sender, ms.receiver, ms.payload, tr.getPrev(entry)) +} + +ghost +decreases +requires ms.Matches(entry) +ensures ms.MatchProperties(entry, entry) +func (ms MessageSearcher) MatchPropertiesReflexive(snapshot, entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires entry.isSuffix(snapshot) +requires ms.Matches(entry) +requires !ms.Matches(snapshot) +requires ms.MatchProperties(tr.getPrev(snapshot), entry) +ensures ms.MatchProperties(snapshot, entry) +func (ms MessageSearcher) MatchPropertiesTransitive(snapshot, entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires ms.Occurs(entry) +ensures (!entry.isRoot() && ms.Occurs(tr.getPrev(entry))) || ms.Matches(entry) +func (ms MessageSearcher) OccursImpliesAnEventualMatch(entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires noPerm < p && p <= writePerm +requires ms.Props() +requires ms.Matches(entry) +requires acc(tri.validTrace(ms.Ctx(), entry), p) +ensures ms.PureEntryInv(entry) +ensures acc(tri.validTrace(ms.Ctx(), entry), p) +func (ms MessageSearcher) ExtractPureEntryInv(entry tr.TraceEntry, p perm) { + assert unfolding acc(tri.validTrace(ms.Ctx(), entry), p) in true +} +// end of MessageSearcher's implementation + + +ghost +decreases +requires m.Mem(ctx, owner) +requires snap.isSuffix(m.Snapshot(ctx, owner)) +requires snap.messageOccurs(sender, receiver, payload) +ensures m.Mem(ctx, owner) +ensures prev.isSuffix(snap) && prev.isMessage() +ensures prev.isSuffix(m.Snapshot(ctx, owner)) +ensures prev.isMessageAt(sender, receiver, payload) +ensures tri.messageInv(m.Ctx(ctx, owner), sender, receiver, payload, tr.getPrev(prev)) +ensures old(m.Snapshot(ctx, owner)) == m.Snapshot(ctx, owner) +ensures m.ImmutableState(ctx, owner) == old(m.ImmutableState(ctx, owner)) +func (m *TraceManager) MessageOccursImpliesMessageInvWithSnap(ctx tri.TraceContext, owner Client, snap tr.TraceEntry, sender, receiver p.Principal, payload tm.Term) (prev tr.TraceEntry) { + searcher := MessageSearcher{ ctx, sender, receiver, payload } + // the following assert stmt is necessary to derive that `ctx != nil`: + assert unfolding m.Mem(ctx, owner) in true + prev = m.findEntryWithPureInvWithSnap(searcher, owner, snap) + prev.isSuffixTransitive(snap, m.Snapshot(ctx, owner)) +} diff --git a/ReusableVerificationLibrary/tracemanager/nonce-lemmas.gobra b/ReusableVerificationLibrary/tracemanager/nonce-lemmas.gobra new file mode 100644 index 0000000..c08c6e8 --- /dev/null +++ b/ReusableVerificationLibrary/tracemanager/nonce-lemmas.gobra @@ -0,0 +1,113 @@ +package tracemanager + +import tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" +import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" +import tri "github.com/viperproject/ReusableProtocolVerificationLibrary/traceinvariant" + + +type NonceSearcher struct { + ctx tri.TraceContext + nonce tm.Term +} +NonceSearcher implements TraceSearcher + +ghost +decreases +pure func (ns NonceSearcher) Props() bool { + return ns.ctx != nil && ns.ctx.Props() +} + +ghost +decreases +requires ns.Props() +ensures res != nil && res.Props() +pure func (ns NonceSearcher) Ctx() (res tri.TraceContext) { + return ns.ctx +} + +ghost +decreases +pure func (ns NonceSearcher) Matches(entry tr.TraceEntry) bool { + return entry.isNonceAt(ns.nonce) +} + +ghost +decreases +requires entry.isSuffix(snapshot) +requires ns.Matches(entry) +pure func (ns NonceSearcher) MatchProperties(snapshot, entry tr.TraceEntry) bool { + return true +} + +ghost +decreases +pure func (ns NonceSearcher) Occurs(entry tr.TraceEntry) bool { + return entry.OnlyNonceOccurs(ns.nonce) +} + +ghost +decreases +requires ns.Props() +pure func (ns NonceSearcher) PureEntryInv(entry tr.TraceEntry) bool { + return tri.pureRandInv(ns.ctx, ns.nonce, tr.getPrev(entry)) +} + +ghost +decreases +requires ns.Matches(entry) +ensures ns.MatchProperties(entry, entry) +func (ns NonceSearcher) MatchPropertiesReflexive(snapshot, entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires entry.isSuffix(snapshot) +requires ns.Matches(entry) +requires !ns.Matches(snapshot) +requires ns.MatchProperties(tr.getPrev(snapshot), entry) +ensures ns.MatchProperties(snapshot, entry) +func (ns NonceSearcher) MatchPropertiesTransitive(snapshot, entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires ns.Occurs(entry) +ensures (!entry.isRoot() && ns.Occurs(tr.getPrev(entry))) || ns.Matches(entry) +func (ns NonceSearcher) OccursImpliesAnEventualMatch(entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires noPerm < p && p <= writePerm +requires ns.Props() +requires ns.Matches(entry) +requires acc(tri.validTrace(ns.Ctx(), entry), p) +ensures ns.PureEntryInv(entry) +ensures acc(tri.validTrace(ns.Ctx(), entry), p) +func (ns NonceSearcher) ExtractPureEntryInv(entry tr.TraceEntry, p perm) { + unfold acc(tri.validTrace(ns.Ctx(), entry), p) + assert unfolding acc(tri.randInv(ns.Ctx(), ns.nonce, tr.getPrev(entry)), p) in true + fold acc(tri.validTrace(ns.Ctx(), entry), p) +} +// end of NonceSearcher's implementation + + +ghost +decreases +requires m.Mem(ctx, owner) +requires (m.Snapshot(ctx, owner)).OnlyNonceOccurs(nonce) +ensures m.Mem(ctx, owner) +ensures prev.isSuffix(m.Snapshot(ctx, owner)) && prev.isNonce() +ensures prev.isNonceAt(nonce) +ensures tri.pureRandInv(ctx, nonce, tr.getPrev(prev)) +ensures old(m.Snapshot(ctx, owner)) == m.Snapshot(ctx, owner) +ensures m.ImmutableState(ctx, owner) == old(m.ImmutableState(ctx, owner)) +func (m *TraceManager) NonceOccursImpliesRandInv(ctx tri.TraceContext, owner Client, nonce tm.Term) (prev tr.TraceEntry) { + searcher := NonceSearcher{ ctx, nonce } + // the following assert stmt is necessary to derive that `ctx != nil`: + assert unfolding m.Mem(ctx, owner) in true + prev = m.findEntryWithPureInv(searcher, owner) +} diff --git a/ReusableVerificationLibrary/tracemanager/public-term-lemmas.gobra b/ReusableVerificationLibrary/tracemanager/public-term-lemmas.gobra new file mode 100644 index 0000000..a5051ab --- /dev/null +++ b/ReusableVerificationLibrary/tracemanager/public-term-lemmas.gobra @@ -0,0 +1,114 @@ +package tracemanager + +import tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" +import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" +import tri "github.com/viperproject/ReusableProtocolVerificationLibrary/traceinvariant" + + +type PublicTermSearcher struct { + ctx tri.TraceContext + term tm.Term +} +PublicTermSearcher implements TraceSearcher + +ghost +decreases +pure func (ps PublicTermSearcher) Props() bool { + return ps.ctx != nil && ps.ctx.Props() +} + +ghost +decreases +requires ps.Props() +ensures res != nil && res.Props() +pure func (ps PublicTermSearcher) Ctx() (res tri.TraceContext) { + return ps.ctx +} + +ghost +decreases +pure func (ps PublicTermSearcher) Matches(entry tr.TraceEntry) bool { + return entry.isRoot() && ps.term in entry.getPublicTerms() +} + +ghost +decreases +requires entry.isSuffix(snapshot) +requires ps.Matches(entry) +pure func (ps PublicTermSearcher) MatchProperties(snapshot, entry tr.TraceEntry) bool { + return true +} + +ghost +decreases +pure func (ps PublicTermSearcher) Occurs(entry tr.TraceEntry) bool { + return ps.term in entry.getPublicTerms() +} + +ghost +decreases +requires ps.Props() +pure func (ps PublicTermSearcher) PureEntryInv(entry tr.TraceEntry) bool { + return entry.isRoot() && tri.publicInv(ps.Ctx(), entry.getPublicTerms(), entry) +} + +ghost +decreases +requires ps.Matches(entry) +ensures ps.MatchProperties(entry, entry) +func (ps PublicTermSearcher) MatchPropertiesReflexive(snapshot, entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires entry.isSuffix(snapshot) +requires ps.Matches(entry) +requires !ps.Matches(snapshot) +requires ps.MatchProperties(tr.getPrev(snapshot), entry) +ensures ps.MatchProperties(snapshot, entry) +func (ps PublicTermSearcher) MatchPropertiesTransitive(snapshot, entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires ps.Occurs(entry) +ensures (!entry.isRoot() && ps.Occurs(tr.getPrev(entry))) || ps.Matches(entry) +func (ps PublicTermSearcher) OccursImpliesAnEventualMatch(entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires noPerm < p && p <= writePerm +requires ps.Props() +requires ps.Matches(entry) +requires acc(tri.validTrace(ps.Ctx(), entry), p) +ensures ps.PureEntryInv(entry) +ensures acc(tri.validTrace(ps.Ctx(), entry), p) +func (ps PublicTermSearcher) ExtractPureEntryInv(entry tr.TraceEntry, p perm) { + assert unfolding acc(tri.validTrace(ps.Ctx(), entry), p) in true +} +// end of PublicTermSearcher's implementation + + +ghost +decreases +requires m.Mem(ctx, owner) +requires snap.isSuffix(m.Snapshot(ctx, owner)) +requires term in snap.getPublicTerms() +ensures m.Mem(ctx, owner) +ensures prev.isSuffix(snap) && prev.isRoot() +ensures prev.isSuffix(m.Snapshot(ctx, owner)) +ensures tri.publicInv(m.Ctx(ctx, owner), snap.getPublicTerms(), prev) +ensures old(m.Snapshot(ctx, owner)) == m.Snapshot(ctx, owner) +ensures m.ImmutableState(ctx, owner) == old(m.ImmutableState(ctx, owner)) +func (m *TraceManager) PublicTermImpliesPublicInvWithSnap(ctx tri.TraceContext, owner Client, snap tr.TraceEntry, term tm.Term) (prev tr.TraceEntry) { + searcher := PublicTermSearcher{ ctx, term } + // the following assert stmt is necessary to derive that `ctx != nil`: + assert unfolding m.Mem(ctx, owner) in true + prev = m.findEntryWithPureInvWithSnap(searcher, owner, snap) + prev.getPublicTermsMonotonic(snap) + prev.isSuffixTransitive(snap, m.Snapshot(ctx, owner)) +} diff --git a/ReusableVerificationLibrary/tracemanager/published-term-lemma.gobra b/ReusableVerificationLibrary/tracemanager/published-term-lemma.gobra new file mode 100644 index 0000000..623b732 --- /dev/null +++ b/ReusableVerificationLibrary/tracemanager/published-term-lemma.gobra @@ -0,0 +1,114 @@ +package tracemanager + +import tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" +import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" +import tri "github.com/viperproject/ReusableProtocolVerificationLibrary/traceinvariant" + + +type PublishedTermSearcher struct { + ctx tri.TraceContext + term tm.Term +} +PublishedTermSearcher implements TraceSearcher + +ghost +decreases +pure func (ps PublishedTermSearcher) Props() bool { + return ps.ctx != nil && ps.ctx.Props() +} + +ghost +decreases +requires ps.Props() +ensures res != nil && res.Props() +pure func (ps PublishedTermSearcher) Ctx() (res tri.TraceContext) { + return ps.ctx +} + +ghost +decreases +pure func (ps PublishedTermSearcher) Matches(entry tr.TraceEntry) bool { + return entry.isPublic() && ps.term == tr.getPayload(entry) +} + +ghost +decreases +requires entry.isSuffix(snapshot) +requires ps.Matches(entry) +pure func (ps PublishedTermSearcher) MatchProperties(snapshot, entry tr.TraceEntry) bool { + return true +} + +ghost +decreases +pure func (ps PublishedTermSearcher) Occurs(entry tr.TraceEntry) bool { + return ps.term in entry.getTermsMadePublic() +} + +ghost +decreases +requires ps.Props() +pure func (ps PublishedTermSearcher) PureEntryInv(entry tr.TraceEntry) bool { + return tri.madePublicInv(ps.ctx, ps.term, tr.getPrev(entry)) +} + +ghost +decreases +requires ps.Matches(entry) +ensures ps.MatchProperties(entry, entry) +func (ps PublishedTermSearcher) MatchPropertiesReflexive(snapshot, entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires entry.isSuffix(snapshot) +requires ps.Matches(entry) +requires !ps.Matches(snapshot) +requires ps.MatchProperties(tr.getPrev(snapshot), entry) +ensures ps.MatchProperties(snapshot, entry) +func (ps PublishedTermSearcher) MatchPropertiesTransitive(snapshot, entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires ps.Occurs(entry) +ensures (!entry.isRoot() && ps.Occurs(tr.getPrev(entry))) || ps.Matches(entry) +func (ps PublishedTermSearcher) OccursImpliesAnEventualMatch(entry tr.TraceEntry) { + // no body needed +} + +ghost +decreases +requires noPerm < p && p <= writePerm +requires ps.Props() +requires ps.Matches(entry) +requires acc(tri.validTrace(ps.Ctx(), entry), p) +ensures ps.PureEntryInv(entry) +ensures acc(tri.validTrace(ps.Ctx(), entry), p) +func (ps PublishedTermSearcher) ExtractPureEntryInv(entry tr.TraceEntry, p perm) { + assert unfolding acc(tri.validTrace(ps.Ctx(), entry), p) in true +} +// end of PublishedTermSearcher's implementation + + +ghost +decreases +requires m.Mem(ctx, owner) +requires snap.isSuffix(m.Snapshot(ctx, owner)) +requires term in snap.getTermsMadePublic() +ensures m.Mem(ctx, owner) +ensures prev.isSuffix(snap) +ensures prev.isSuffix(m.Snapshot(ctx, owner)) +ensures prev.isPublic() +ensures tri.madePublicInv(m.Ctx(ctx, owner), term, tr.getPrev(prev)) +ensures old(m.Snapshot(ctx, owner)) == m.Snapshot(ctx, owner) +ensures m.ImmutableState(ctx, owner) == old(m.ImmutableState(ctx, owner)) +func (m *TraceManager) PublishedTermImpliesMadePublicInvWithSnap(ctx tri.TraceContext, owner Client, snap tr.TraceEntry, term tm.Term) (prev tr.TraceEntry) { + searcher := PublishedTermSearcher{ ctx, term } + // the following assert stmt is necessary to derive that `ctx != nil`: + assert unfolding m.Mem(ctx, owner) in true + prev = m.findEntryWithPureInvWithSnap(searcher, owner, snap) + prev.isSuffixTransitive(snap, m.Snapshot(ctx, owner)) +} diff --git a/ReusableVerificationLibrary/tracemanager/search.gobra b/ReusableVerificationLibrary/tracemanager/search.gobra new file mode 100644 index 0000000..34e095d --- /dev/null +++ b/ReusableVerificationLibrary/tracemanager/search.gobra @@ -0,0 +1,339 @@ +package tracemanager + +import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" +import tri "github.com/viperproject/ReusableProtocolVerificationLibrary/traceinvariant" + + +/** represents the state after acquiring the mutex to then use it prove a lemma */ +pred (m *TraceManager) memLocked(ctx tri.TraceContext, owner Client, lastSeenSnapshot, snapshot tr.TraceEntry) { + acc(m) && 0 < m.nClients && + ctx != nil && isComparable(ctx) && ctx.Props() && + m.mutex.ClientHistoryMutexStateLocked(ManagerInv{ ctx }, owner, snapshot) && + m.mutex.LastSeenValueLocked(ManagerInv{ ctx }, owner, snapshot) == lastSeenSnapshot && + (ManagerInv{ ctx }).TwoStepValueInv(lastSeenSnapshot, snapshot) // && + // unfolding m.mutex.clientHistoryMutexState(ManagerInv{ ctx }, owner, true) in *m.mutex.currentValue == snapshot +} + +ghost +decreases +requires m.Mem(ctx, owner) +ensures tri.validTrace(ctx, snapshot) +ensures old(m.Snapshot(ctx, owner)) == lastSeenSnapshot +ensures lastSeenSnapshot.isSuffix(snapshot) +ensures m.memLocked(ctx, owner, lastSeenSnapshot, snapshot) +ensures old(unfolding m.Mem(ctx, owner) in m.mutex) == unfolding m.memLocked(ctx, owner, lastSeenSnapshot, snapshot) in m.mutex +/** utility function to lock mutex and get the valid trace predicate */ +func (m *TraceManager) acquireValidTrace(ctx tri.TraceContext, owner Client) (lastSeenSnapshot, snapshot tr.TraceEntry) { + // to obtain the validTrace predicate instance, we have to aquire the lock and apply monotonicity: + inv := ManagerInv{ ctx } + lastSeenSnapshot := m.Snapshot(ctx, owner) + unfold m.Mem(ctx, owner) + snapshot = m.mutex.Lock(inv, owner) + unfold inv.CurrentValueInv(snapshot) + fold m.memLocked(ctx, owner, lastSeenSnapshot, snapshot) +} + +ghost +decreases +requires m.memLocked(ctx, owner, lastSeenSnapshot, snapshot) +requires tri.validTrace(ctx, snapshot) +ensures m.Mem(ctx, owner) +ensures m.Snapshot(ctx, owner) == lastSeenSnapshot +ensures (unfolding m.Mem(ctx, owner) in m.mutex) == old(unfolding m.memLocked(ctx, owner, lastSeenSnapshot, snapshot) in m.mutex) +func (m *TraceManager) releaseValidTrace(ctx tri.TraceContext, owner Client, lastSeenSnapshot, snapshot tr.TraceEntry) { + inv := ManagerInv{ ctx } + unfold m.memLocked(ctx, owner, lastSeenSnapshot, snapshot) + fold inv.CurrentValueInv(snapshot) + // there is no need to update the snapshot + m.mutex.UnlockWithSnapshot(inv, owner, snapshot, snapshot, lastSeenSnapshot) + fold m.Mem(ctx, owner) +} + +type TraceSearcher interface { + ghost + decreases + pure Props() bool + + ghost + decreases + requires Props() + ensures res != nil && res.Props() + pure Ctx() (res tri.TraceContext) + + ghost + decreases + pure Matches(entry tr.TraceEntry) bool + + ghost + decreases + requires entry.isSuffix(snapshot) + requires Matches(entry) + /** + * states additional (pure) properties about the entry that `Matches` + */ + pure MatchProperties(snapshot, entry tr.TraceEntry) bool + + ghost + decreases + pure Occurs(entry tr.TraceEntry) bool + + ghost + decreases + requires Props() + pure PureEntryInv(entry tr.TraceEntry) bool + + ghost + decreases + requires Matches(entry) + ensures MatchProperties(entry, entry) + MatchPropertiesReflexive(snapshot, entry tr.TraceEntry) + + ghost + decreases + requires entry.isSuffix(snapshot) + requires Matches(entry) + requires !Matches(snapshot) + requires MatchProperties(tr.getPrev(snapshot), entry) + ensures MatchProperties(snapshot, entry) + MatchPropertiesTransitive(snapshot, entry tr.TraceEntry) + + ghost + decreases + requires Occurs(entry) + ensures (!entry.isRoot() && Occurs(tr.getPrev(entry))) || Matches(entry) + /** + * Occurs implies that either there is a match at the current entry or the right + * entry will occur further in the trace's past. This then also implies that we + * cannot have reached the root yet + */ + OccursImpliesAnEventualMatch(entry tr.TraceEntry) + + ghost + decreases + requires noPerm < p && p <= writePerm + requires Props() + requires Matches(entry) + requires acc(tri.validTrace(Ctx(), entry), p) + ensures PureEntryInv(entry) + ensures acc(tri.validTrace(Ctx(), entry), p) + /** + * pure component of the `validTrace` invariant that belongs to the entry that + * was searched. + */ + ExtractPureEntryInv(entry tr.TraceEntry, p perm) +} + +ghost +decreases validTraceEntry.traceLen() +requires noPerm < p && p <= writePerm +requires searcher != nil && searcher.Props() +requires acc(tri.validTrace(searcher.Ctx(), validTraceEntry), p) +requires entry.isSuffix(validTraceEntry) +requires searcher.Occurs(entry) +ensures prev.isSuffix(entry) +ensures searcher.Matches(prev) +ensures searcher.MatchProperties(entry, prev) +ensures searcher.PureEntryInv(prev) +ensures acc(tri.validTrace(searcher.Ctx(), prev), p) +ensures acc(tri.validTrace(searcher.Ctx(), prev), p) --* acc(tri.validTrace(searcher.Ctx(), validTraceEntry), p) +// this lemma is part of the manager as it requires the validTrace predicate +// we have two separate TraceEntry args (`validTraceEntry` and `entry`) such that the validTrace predicate instance can be provided for a possibly longer trace +// this indirectly strengthens the postcondition because `prev` is a suffix of `entry` instead of `validTraceEntry` +/** + * Searches on the trace for an existing entry and returns this entry. + * Furthermore, the (impure) valid trace invariant at the time point back then is returned. + */ +func (m *TraceManager) findEntryWithInv(searcher TraceSearcher, validTraceEntry, entry tr.TraceEntry, p perm) (prev tr.TraceEntry) { + ctx := searcher.Ctx() + if validTraceEntry == entry { + unfold acc(tri.validTrace(ctx, validTraceEntry), p) + if searcher.Matches(validTraceEntry) { + // entry has been found + prev = validTraceEntry + searcher.MatchPropertiesReflexive(prev, prev) + fold acc(tri.validTrace(searcher.Ctx(), validTraceEntry), p) + searcher.ExtractPureEntryInv(prev, p) + package acc(tri.validTrace(searcher.Ctx(), prev), p) --* acc(tri.validTrace(searcher.Ctx(), validTraceEntry), p) + } else { + // recurse + searcher.OccursImpliesAnEventualMatch(validTraceEntry) + prev = m.findEntryWithInv(searcher, tr.getPrev(validTraceEntry), tr.getPrev(validTraceEntry), p) + searcher.MatchPropertiesTransitive(validTraceEntry, prev) + package acc(tri.validTrace(ctx, prev), p) --* acc(tri.validTrace(ctx, validTraceEntry), p) { + apply acc(tri.validTrace(ctx, prev), p) --* acc(tri.validTrace(ctx, tr.getPrev(validTraceEntry)), p) + fold acc(tri.validTrace(ctx, validTraceEntry), p) + } + } + } else { + unfold acc(tri.validTrace(ctx, validTraceEntry), p) + prev = m.findEntryWithInv(searcher, tr.getPrev(validTraceEntry), entry, p) + package acc(tri.validTrace(ctx, prev), p) --* acc(tri.validTrace(ctx, validTraceEntry), p) { + apply acc(tri.validTrace(ctx, prev), p) --* acc(tri.validTrace(ctx, tr.getPrev(validTraceEntry)), p) + fold acc(tri.validTrace(ctx, validTraceEntry), p) + } + } +} + +ghost +decreases +requires searcher != nil && searcher.Props() +requires m.Mem(searcher.Ctx(), owner) +requires searcher.Occurs(m.Snapshot(searcher.Ctx(), owner)) +ensures m.Mem(searcher.Ctx(), owner) +ensures searcher.Occurs(m.Snapshot(searcher.Ctx(), owner)) +ensures prev.isSuffix(m.Snapshot(searcher.Ctx(), owner)) && searcher.Matches( + prev) +ensures searcher.PureEntryInv(prev) +ensures searcher.MatchProperties(m.Snapshot(searcher.Ctx(), owner), prev) +ensures old(m.Snapshot(searcher.Ctx(), owner)) == m.Snapshot(searcher.Ctx(), owner) +ensures m.ImmutableState(searcher.Ctx(), owner) == old(m.ImmutableState(searcher.Ctx(), owner)) +/** + * Searches on the trace for an existing entry and returns this entry. + * Furthermore, the pure invariant for the located entry is returned. + */ +func (m *TraceManager) findEntryWithPureInv(searcher TraceSearcher, owner Client) (prev tr.TraceEntry) { + prev = m.findEntryWithPureInvWithSnap(searcher, owner, m.Snapshot(searcher.Ctx(), owner)) +} + +ghost +decreases +requires searcher != nil && searcher.Props() +requires m.Mem(searcher.Ctx(), owner) +requires snap.isSuffix(m.Snapshot(searcher.Ctx(), owner)) +requires searcher.Occurs(snap) +ensures m.Mem(searcher.Ctx(), owner) +ensures prev.isSuffix(snap) && searcher.Matches(prev) +ensures searcher.PureEntryInv(prev) +ensures searcher.MatchProperties(snap, prev) +ensures old(m.Snapshot(searcher.Ctx(), owner)) == m.Snapshot(searcher.Ctx(), owner) +ensures m.ImmutableState(searcher.Ctx(), owner) == old(m.ImmutableState(searcher.Ctx(), owner)) +/** + * Searches on the trace for an existing entry and returns this entry. + * Furthermore, the pure invariant for the located entry is returned. + */ +func (m *TraceManager) findEntryWithPureInvWithSnap(searcher TraceSearcher, owner Client, snap tr.TraceEntry) (prev tr.TraceEntry) { + // to obtain the validTrace predicate instance, we have to aquire the lock and apply monotonicity: + lastSeenSnapshot, snapshot := m.acquireValidTrace(searcher.Ctx(), owner) + snap.isSuffixTransitive(lastSeenSnapshot, snapshot) + prev = m.findEntryWithInv(searcher, snapshot, snap, 1/2) + apply acc(tri.validTrace(searcher.Ctx(), prev), 1/2) --* acc(tri.validTrace(searcher.Ctx(), snapshot), 1/2) + m.releaseValidTrace(searcher.Ctx(), owner, lastSeenSnapshot, snapshot) +} + +/** this is the same as the `validTrace` predicate but without the recursive component */ +pred localValidTrace(ctx tri.TraceContext, t tr.TraceEntry) { + ctx != nil && ctx.Props() && + (t.isRoot() ==> tri.publicInv(ctx, tr.getPublicTerms(t), t)) && + (t.isEvent() ==> ctx.eventInv(tr.getPrincipal(t), tr.getEvent(t), tr.getPrev(t))) && + (t.isMessage() ==> tri.messageInv(ctx, tr.getSender(t), tr.getReceiver(t), tr.getPayload(t), tr.getPrev(t))) && + (t.isDropMessage() ==> tri.messageInv(ctx, tr.getSender(t), tr.getReceiver(t), tr.getPayload(t), tr.getPrev(t))) && + (t.isNonce() ==> tri.randInv(ctx, tr.getNonce(t), tr.getPrev(t))) && + (t.isPublic() ==> tri.madePublicInv(ctx, tr.getPayload(t), tr.getPrev(t))) +} + +ghost +decreases validTraceEntry.traceLen() +requires searcher1 != nil && searcher1.Props() +requires searcher2 != nil && searcher2.Props() +requires isComparable(searcher1.Ctx()) && searcher1.Ctx() == searcher2.Ctx() +requires tri.validTrace(searcher1.Ctx(), validTraceEntry) +requires searcher1.Occurs(validTraceEntry) +requires searcher2.Occurs(validTraceEntry) +ensures s1.isSuffix(validTraceEntry) +ensures searcher1.Matches(s1) +ensures searcher1.MatchProperties(validTraceEntry, s1) +ensures s2.isSuffix(validTraceEntry) +ensures searcher2.Matches(s2) +ensures searcher2.MatchProperties(validTraceEntry, s2) +ensures s1 == s2 ==> + localValidTrace(searcher1.Ctx(), s1) && + (localValidTrace(searcher1.Ctx(), s1) --* tri.validTrace(searcher1.Ctx(), validTraceEntry)) +ensures s1 != s2 ==> + localValidTrace(searcher1.Ctx(), s1) && + localValidTrace(searcher2.Ctx(), s2) && + ((localValidTrace(searcher1.Ctx(), s1) && localValidTrace(searcher2.Ctx(), s2)) --* tri.validTrace(searcher1.Ctx(), validTraceEntry)) +// this lemma is part of the manager as it requires the validTrace predicate +func (m *TraceManager) findEntriesWithInv(searcher1, searcher2 TraceSearcher, validTraceEntry tr.TraceEntry) (s1, s2 tr.TraceEntry) { + ctx := searcher1.Ctx() + s1Found := false + s2Found := false + unfold tri.validTrace(ctx, validTraceEntry) + if searcher1.Matches(validTraceEntry) { + // entry 1 found + s1Found = true + s1 = validTraceEntry + searcher1.MatchPropertiesReflexive(s1, s1) + } + if searcher2.Matches(validTraceEntry) { + // entry 2 found + s2Found = true + s2 = validTraceEntry + searcher2.MatchPropertiesReflexive(s2, s2) + } + + // apply some lemmas before potentially recursing on the trace: + if !s1Found { + searcher1.OccursImpliesAnEventualMatch(validTraceEntry) + } + if !s2Found { + searcher2.OccursImpliesAnEventualMatch(validTraceEntry) + } + + // recurse if necessary + if s1Found && s2Found { + fold localValidTrace(ctx, s1) + package localValidTrace(ctx, s1) --* tri.validTrace(ctx, validTraceEntry) { + unfold localValidTrace(ctx, s1) + fold tri.validTrace(ctx, validTraceEntry) + } + } else if s1Found && !s2Found { + // s1 is more recent than s2 + s2 = m.findEntryWithInv(searcher2, tr.getPrev(validTraceEntry), tr.getPrev(validTraceEntry), writePerm) + fold localValidTrace(ctx, s1) + unfold tri.validTrace(ctx, s2) + fold localValidTrace(ctx, s2) + package (localValidTrace(ctx, s1) && localValidTrace(ctx, s2)) --* tri.validTrace(ctx, validTraceEntry) { + unfold localValidTrace(ctx, s2) + fold tri.validTrace(ctx, s2) + apply tri.validTrace(ctx, s2) --* tri.validTrace(ctx, tr.getPrev(validTraceEntry)) + unfold localValidTrace(ctx, s1) + fold tri.validTrace(ctx, validTraceEntry) + } + } else if !s1Found && s2Found { + // s2 is more recent than s1 + s1 = m.findEntryWithInv(searcher1, tr.getPrev(validTraceEntry), tr.getPrev(validTraceEntry), writePerm) + unfold tri.validTrace(ctx, s1) + fold localValidTrace(ctx, s1) + fold localValidTrace(ctx, s2) + package (localValidTrace(ctx, s1) && localValidTrace(ctx, s2)) --* tri.validTrace(ctx, validTraceEntry) { + unfold localValidTrace(ctx, s1) + fold tri.validTrace(ctx, s1) + apply tri.validTrace(ctx, s1) --* tri.validTrace(ctx, tr.getPrev(validTraceEntry)) + unfold localValidTrace(ctx, s2) + fold tri.validTrace(ctx, validTraceEntry) + } + } else { + s1, s2 = m.findEntriesWithInv(searcher1, searcher2, tr.getPrev(validTraceEntry)) + s1.isSuffixTransitive(tr.getPrev(validTraceEntry), validTraceEntry) + s2.isSuffixTransitive(tr.getPrev(validTraceEntry), validTraceEntry) + if s1 == s2 { + package localValidTrace(ctx, s1) --* tri.validTrace(ctx, validTraceEntry) { + apply localValidTrace(ctx, s1) --* tri.validTrace(ctx, tr.getPrev(validTraceEntry)) + fold tri.validTrace(ctx, validTraceEntry) + } + } else { + package (localValidTrace(searcher1.Ctx(), s1) && localValidTrace(searcher2.Ctx(), s2)) --* tri.validTrace(searcher1.Ctx(), validTraceEntry) { + apply (localValidTrace(searcher1.Ctx(), s1) && localValidTrace(searcher2.Ctx(), s2)) --* tri.validTrace(searcher1.Ctx(), tr.getPrev(validTraceEntry)) + fold tri.validTrace(ctx, validTraceEntry) + } + } + } + + // apply some lemma after recursing + if !s1Found { + searcher1.MatchPropertiesTransitive(validTraceEntry, s1) + } + if !s2Found { + searcher2.MatchPropertiesTransitive(validTraceEntry, s2) + } +} diff --git a/ReusableVerificationLibrary/usage/usage.gobra b/ReusableVerificationLibrary/usage/usage.gobra new file mode 100644 index 0000000..3c3ab83 --- /dev/null +++ b/ReusableVerificationLibrary/usage/usage.gobra @@ -0,0 +1,113 @@ +package usage + + +type Usage domain { + // constructors + // type 0 + func PkeKey(string) Usage + // type 1 + func DhKey(string) Usage + // type 2 + func Nonce(string) Usage + // type 3 + func AeadKey(string) Usage + // type 4 + /** denotes that a key is meant as input to a KDF function */ + func KdfKey(string) Usage + // type 5 + func SigningKey(string) Usage + + // WARNING: adapt first axiom if another Usage is added! + + // deconstructors + func GetUsageType(Usage) int + func GetUsageString(Usage) string + + axiom { // every usage belongs to a known type + forall u Usage :: { GetUsageType(u) } 0 <= GetUsageType(u) && GetUsageType(u) <= 5 + } + + axiom { // PkeKey is injective + forall s string :: { PkeKey(s) } GetUsageType(PkeKey(s)) == 0 && + GetUsageString(PkeKey(s)) == s + } + axiom { // PkeKey implies its construction + forall u Usage :: { GetUsageType(u) } GetUsageType(u) == 0 ==> u == PkeKey(GetUsageString(u)) + } + + axiom { // DhKey is injective + forall s string :: { DhKey(s) } GetUsageType(DhKey(s)) == 1 && + GetUsageString(DhKey(s)) == s + } + axiom { // DhKey implies its construction + forall u Usage :: { GetUsageType(u) } GetUsageType(u) == 1 ==> u == DhKey(GetUsageString(u)) + } + + axiom { // Nonce is injective + forall s string :: { Nonce(s) } GetUsageType(Nonce(s)) == 2 && + GetUsageString(Nonce(s)) == s + } + axiom { // Nonce implies its construction + forall u Usage :: { GetUsageType(u) } GetUsageType(u) == 2 ==> u == Nonce(GetUsageString(u)) + } + + axiom { // AeadKey is injective + forall s string :: { AeadKey(s) } GetUsageType(AeadKey(s)) == 3 && + GetUsageString(AeadKey(s)) == s + } + axiom { // AeadKey implies its construction + forall u Usage :: { GetUsageType(u) } GetUsageType(u) == 3 ==> u == AeadKey(GetUsageString(u)) + } + + axiom { // KdfKey is injective + forall s string :: { KdfKey(s) } GetUsageType(KdfKey(s)) == 4 && + GetUsageString(KdfKey(s)) == s + } + axiom { // KdfKey implies its construction + forall u Usage :: { GetUsageType(u) } GetUsageType(u) == 4 ==> u == KdfKey(GetUsageString(u)) + } + + axiom { // SigningKey is injective + forall s string :: { SigningKey(s) } GetUsageType(SigningKey(s)) == 5 && + GetUsageString(SigningKey(s)) == s + } + axiom { // SigningKey implies its construction + forall u Usage :: { GetUsageType(u) } GetUsageType(u) == 5 ==> u == SigningKey(GetUsageString(u)) + } +} + +ghost +decreases +pure func (u Usage) IsPkeKey() bool { + return GetUsageType(u) == 0 +} + +ghost +decreases +pure func (u Usage) IsDhKey() bool { + return GetUsageType(u) == 1 +} + +ghost +decreases +pure func (u Usage) IsNonce() bool { + return GetUsageType(u) == 2 +} + +ghost +decreases +pure func (u Usage) IsAeadKey() bool { + return GetUsageType(u) == 3 +} + +ghost +decreases +pure func (u Usage) IsKdfKey() bool { + return GetUsageType(u) == 4 +} + +ghost +decreases +pure func (u Usage) IsSigningKey() bool { + return GetUsageType(u) == 5 +} diff --git a/ReusableVerificationLibrary/usagecontext/usage-context.gobra b/ReusableVerificationLibrary/usagecontext/usage-context.gobra new file mode 100644 index 0000000..11c797d --- /dev/null +++ b/ReusableVerificationLibrary/usagecontext/usage-context.gobra @@ -0,0 +1,47 @@ +package usagecontext + +import tm "github.com/viperproject/ReusableProtocolVerificationLibrary/term" +import tr "github.com/viperproject/ReusableProtocolVerificationLibrary/trace" +import u "github.com/viperproject/ReusableProtocolVerificationLibrary/usage" + + +type UsageOption = option[u.Usage] + +type UsageContext interface { + ghost + decreases + pure GetUsage(t tm.Term) UsageOption + + ghost + decreases + pure PkePred(t tr.TraceEntry, usageString string, plaintext, pk tm.Term) bool + + ghost + decreases + requires t1.isSuffix(t2) + requires PkePred(t1, usageString, plaintext, pk) + ensures PkePred(t2, usageString, plaintext, pk) + PkePredMonotonic(t1, t2 tr.TraceEntry, usageString string, plaintext, pk tm.Term) + + ghost + decreases + pure AeadPred(t tr.TraceEntry, usageString string, key, nonce, plaintext, authtext tm.Term) bool + + ghost + decreases + requires t1.isSuffix(t2) + requires AeadPred(t1, usageString, key, nonce, plaintext, authtext) + ensures AeadPred(t2, usageString, key, nonce, plaintext, authtext) + AeadPredMonotonic(t1, t2 tr.TraceEntry, usageString string, key, nonce, plaintext, authtext tm.Term) + + ghost + decreases + pure SignPred(t tr.TraceEntry, usageString string, msg, sk tm.Term) bool + + ghost + decreases + requires t1.isSuffix(t2) + requires SignPred(t1, usageString, msg, sk) + ensures SignPred(t2, usageString, msg, sk) + SignPredMonotonic(t1, t2 tr.TraceEntry, usageString string, msg, sk tm.Term) +} diff --git a/ReusableVerificationLibrary/verify.sh b/ReusableVerificationLibrary/verify.sh new file mode 100755 index 0000000..149395a --- /dev/null +++ b/ReusableVerificationLibrary/verify.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# this script is invoked as part of CI to verify all packages + +scriptDir=$(dirname "$0") + +# flag whether this script is currently executed as part of a CI +isCi=$CI + +# create .gobra folder if it does not exist yet: +mkdir -p $scriptDir/.gobra + +gobraJar="/gobra/gobra.jar" +additionalGobraArgs="--module github.com/viperproject/ReusableProtocolVerificationLibrary --include .verification --gobraDirectory $scriptDir/.gobra --parallelizeBranches" + +if [ $isCi ]; then + echo -e "\033[0Ksection_start:`date +%s`:verify[collapsed=true]\r\033[0KVerifying packages" +fi +java -Xss128m -jar $gobraJar --recursive -I $scriptDir $additionalGobraArgs +exitCode=$? +if [ $isCi ]; then + echo -e "\033[0Ksection_end:`date +%s`:verify\r\033[0K" +fi + +# set exit code: +exit $exitCode diff --git a/VeriFastPrototype/nsl/compile.sh b/VeriFastPrototype/nsl/compile.sh new file mode 100755 index 0000000..5674b2b --- /dev/null +++ b/VeriFastPrototype/nsl/compile.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +# exit when any command fails +set -e + +SCRIPT_DIR=$(dirname "$0") +VERIFAST=$(which verifast) +VERIFAST_BIN="$(dirname "$VERIFAST")" + +# copy files in this directory and all `.h` files from the library +# to some temporary directory to then verify them: + +# try linux and fall-back to macOS: +TMP_DIR=$(mktemp -d -t verification-XXXXXXXXXX -p "$SCRIPT_DIR/.." 2>/dev/null || mktemp -d "$SCRIPT_DIR/../verification-XXXXXXXXXX") + +# copy reusable verification library: +# LIB_FILES=($(find $SCRIPT_DIR/../reusable_verification_library -type f \( -iname \*.gh -o -iname \*.h \))) +LIB_FILES=($(find $SCRIPT_DIR/../reusable_verification_library -type f \( -iname \*.gh -o -iname \*.h -o -iname \*.c \))) +for LIB_FILE in "${LIB_FILES[@]}" +do + cp "$LIB_FILE" "$TMP_DIR" +done + +# remove the header files containing dummy definitions as we use the NSL-specific ones instead: +rm "$TMP_DIR/protocol_specific_definitions.gh" +rm "$TMP_DIR/protocol_specific_event_params.gh" +rm "$TMP_DIR/protocol_specific_event_lemma.c" + +# additional files needed for successful compilation: +cp "$VERIFAST_BIN/threading.c" "$TMP_DIR" + +# copy NSL: +NSL_FILES=($(find $SCRIPT_DIR/ -type f \( -iname \*.gh -o -iname \*.h -o -iname \*.c \))) + +for FILE in "${NSL_FILES[@]}" +do + FILENAME=$(basename "$FILE") + DEST_FILE="$TMP_DIR/$FILENAME" + if [[ -e "$DEST_FILE" ]] + then + echo "File already exists: $FILE" + exit 1 + else + cp "$FILE" "$DEST_FILE" + fi +done + +# verify all `.c` files in the tmp folder: +C_FILES=($TMP_DIR/*.c) +C_FILES_STR=$(printf "%s " "${C_FILES[@]}") + +# compile initiator and responder +gcc $C_FILES_STR -iquote $VERIFAST_BIN -lcrypto -o nsl + +rm -r $TMP_DIR diff --git a/VeriFastPrototype/nsl/execute.sh b/VeriFastPrototype/nsl/execute.sh new file mode 100755 index 0000000..07edc17 --- /dev/null +++ b/VeriFastPrototype/nsl/execute.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# exit when any command fails +set -e + +SCRIPT_DIR=$(dirname "$0") + +source $SCRIPT_DIR/compile.sh + +function initiator() { + source $SCRIPT_DIR/runInitiator.sh +} + +function responder() { + source $SCRIPT_DIR/runResponder.sh +} + +# execute initiator and responder in parallel and wait until they are done: +RESPONDER_TEMP_FILE=$(mktemp) +responder > $RESPONDER_TEMP_FILE & +# wait for 1 sec to make sure that responder has started up +sleep 1 +INITIATOR_TEMP_FILE=$(mktemp) +initiator > $INITIATOR_TEMP_FILE & +wait + +# print initiator and responder output: +echo -e "\n\n======== Initiator Output ========" +cat $INITIATOR_TEMP_FILE + +echo -e "\n\n======== Responder Output ========" +cat $RESPONDER_TEMP_FILE + +rm $INITIATOR_TEMP_FILE +rm $RESPONDER_TEMP_FILE diff --git a/VeriFastPrototype/nsl/initiator.c b/VeriFastPrototype/nsl/initiator.c new file mode 100644 index 0000000..15cb6a2 --- /dev/null +++ b/VeriFastPrototype/nsl/initiator.c @@ -0,0 +1,453 @@ +#include "initiator.h" +#include +#include +//@ #include "bytes.gh" +//@ #include "labeling.gh" +#include "messages.h" +//@ #include "nsl_pke_pred.gh" +//@ #include "pattern.gh" +//@ #include "security_properties.gh" +//@ #include "subset_helpers.gh" +//@ #include "term.gh" + + +/*@ +predicate NaNonce(Trace snap, int idA, int idB, char *na, Term naT) = + chars(na, NonceLength, ?abs_na) &*& malloc_block(na, NonceLength) &*& + gamma(naT) == abs_na &*& + isLabeled(naT, snap, Readers(cons(idA, cons(idB, nil))), pkePred) == true; + +predicate NbNonce(Trace snap, int idA, int idB, char *nb, Term nbT) = + chars(nb, NonceLength, ?abs_nb) &*& malloc_block(nb, NonceLength) &*& + gamma(nbT) == abs_nb &*& + (containsCorruptId(getCorruptIds(snap), cons(idA, cons(idB, nil))) || isLabeled(nbT, snap, Readers(cons(idA, cons(idB, nil))), pkePred)); + +predicate Mem(struct A* a, int version) = + malloc_block_A(a) &*& + a->labeledLib |-> ?labeledLib &*& LabeledLibMem(labeledLib, ?snap, ?idA, pkePred) &*& + a->version |-> version &*& 0 <= version &*& + a->idA |-> idA &*& + a->pkA |-> ?pkA &*& + a->pkA_len |-> ?pkA_len &*& chars(pkA, pkA_len, ?abs_pkA) &*& malloc_block(pkA, pkA_len) &*& + a->skA |-> ?skA &*& + a->skA_len |-> ?skA_len &*& chars(skA, skA_len, ?abs_skA) &*& malloc_block(skA, skA_len) &*& + a->idB |-> ?idB &*& + a->pkB |-> ?pkB &*& + a->pkB_len |-> ?pkB_len &*& + a->skAT |-> ?skAT &*& + a->skBT |-> ?skBT &*& + abs_skA == gamma(skAT) &*& + isSecretKey(snap, idA, skAT, 1, pkePred) == true &*& + (1 <= version ? + chars(pkB, pkB_len, ?abs_pkB) &*& malloc_block(pkB, pkB_len) &*& + abs_pkB == gamma(publicKey(skBT)) &*& + isPublicKey(snap, idB, publicKey(skBT), skBT, 1, pkePred) == true : true) &*& + (2 <= version ? + NaNonce(snap, idA, idB, ?na, ?naT) &*& + NbNonce(snap, idA, idB, ?nb, ?nbT) &*& + eventOccurs(snap, idA, newEvent(FINISHI, FinishIParams(idA, idB, naT, nbT))) == true &*& + (3 <= version ? + Secrecy(naT, snap, cons(idA, cons(idB, nil)), pkePred) == true &*& + Secrecy(nbT, snap, cons(idA, cons(idB, nil)), pkePred) == true &*& + InjectiveAgreement(snap, idA, idB, FINISHI, FinishIParams(idA, idB, naT, nbT), RESPOND, RespondParams(idA, idB, naT, nbT), cons(idA, cons(idB, nil))) : true) + : true); +@*/ + +struct A *initA(struct IoLib *io_lib, struct TraceManager *tm, int initiator, int responder) +//@ requires IoLibMem(io_lib) &*& TraceManagerMem(tm, initiator, ?snap0, pkePred); +//@ ensures result != 0 ? Mem(result, 0) : emp; +{ + struct A* a = malloc(sizeof(struct A)); + if(a == 0) { + abort(); + } + //@ getPkeCtxt(); + a->labeledLib = createLabeledLib(io_lib, tm, initiator); + if(a->labeledLib == 0) { + abort(); + } + a->version = 0; + a->idA = initiator; + a->idB = responder; + + // generate keypair + //@ close exists(Readers(cons(initiator, nil))); + struct keypair *keys = labeledGeneratePkeKey(a->labeledLib); + if (keys == 0) { + abort(); + } + char *skA = keys->sk; + a->skA = skA; + a->skA_len = keys->sk_len; + a->pkA = keys->pk; + a->pkA_len = keys->pk_len; + free(keys); + //@ assert chars(skA, _, ?abs_sk); + //@ a->skAT = nonce(abs_sk, Readers(cons(initiator, nil))); + + //@ close Mem(a, 0); + return a; +} + +void initiatorNonInjectiveAgreement(struct LabeledLib *lib) +/*@ requires LabeledLibMem(lib, ?snap, ?owner, pkePred) &*& + NaNonce(snap, ?idA, ?idB, ?na, ?naT) &*& + NbNonce(snap, idA, idB, ?nb, ?nbT) &*& + eventOccurs(snap, idA, newEvent(FINISHI, FinishIParams(idA, idB, naT, nbT))) == true; @*/ +/*@ ensures LabeledLibMem(lib, snap, owner, pkePred) &*& + NaNonce(snap, idA, idB, na, naT) &*& + NbNonce(snap, idA, idB, nb, nbT) &*& + NonInjectiveAgreement(snap, idA, idB, FINISHI, FinishIParams(idA, idB, naT, nbT), RESPOND, RespondParams(idA, idB, naT, nbT), cons(idA, cons(idB, nil))) == true; @*/ +{ + //@ ParameterizedEvent ev = newEvent(FINISHI, FinishIParams(idA, idB, naT, nbT)); + //@ close exists(idA); + //@ close exists(ev); + //@ getPkeCtxt(); + labeledEventOccursImpliesEventInv(lib); + //@ assert exists(?finishIWitness); + /*@ + if (!eventOccurs(finishIWitness, idB, newEvent(RESPOND, RespondParams(idA, idB, naT, nbT)))) { + getCorruptIdsMonotonic(finishIWitness, snap); + subset_intersection_helper(getCorruptIds(finishIWitness), getCorruptIds(snap), cons(idA, cons(idB, nil))); + } + @*/ + //@ leakPkeCtxt(); +} + +void initiatorInjectiveAgreement(struct LabeledLib *lib) +/*@ requires LabeledLibMem(lib, ?snap, ?owner, pkePred) &*& + NaNonce(snap, ?idA, ?idB, ?na, ?naT) &*& + NbNonce(snap, idA, idB, ?nb, ?nbT) &*& + eventOccurs(snap, idA, newEvent(FINISHI, FinishIParams(idA, idB, naT, nbT))) == true; @*/ +/*@ ensures LabeledLibMem(lib, snap, owner, pkePred) &*& + NaNonce(snap, idA, idB, na, naT) &*& + NbNonce(snap, idA, idB, nb, nbT) &*& + InjectiveAgreement(snap, idA, idB, FINISHI, FinishIParams(idA, idB, naT, nbT), RESPOND, RespondParams(idA, idB, naT, nbT), cons(idA, cons(idB, nil))); @*/ +{ + initiatorNonInjectiveAgreement(lib); + //@ close exists(idA); + //@ list bothIds = cons(idA, cons(idB, nil)); + //@ ParameterizedEvent finishIEv = newEvent(FINISHI, FinishIParams(idA, idB, naT, nbT)); + //@ close exists(finishIEv); + //@ getPkeCtxt(); + labeledUniqueEventIsUnique(lib); + //@ assert EventIsUnique(snap, idA, finishIEv); + //@ close InjectiveAgreement(snap, idA, idB, FINISHI, FinishIParams(idA, idB, naT, nbT), RESPOND, RespondParams(idA, idB, naT, nbT), bothIds); + //@ Trace finishIWitness = getEventTrace(snap, idA, finishIEv); + /*@ + if (containsCorruptId(getCorruptIds(finishIWitness), bothIds)) { + leak EventIsUnique(snap, idA, finishIEv); + } + @*/ + //@ leakPkeCtxt(); +} + +void initiatorProveSecurityProperties(struct A* a) +//@ requires Mem(a, 2); +//@ ensures Mem(a, 3); +{ + //@ open Mem(a, 2); + struct LabeledLib *lib = a->labeledLib; + + initiatorInjectiveAgreement(lib); + + //@ open NaNonce(?snap, ?idA, ?idB, ?na, ?naT); + //@ list bothIds = cons(idA, cons(idB, nil)); + //@ Label both_label = Readers(bothIds); + //@ close exists(naT); + //@ close exists(bothIds); + //@ getPkeCtxt(); + labeledSecrecyLemma(lib); + //@ close NaNonce(snap, idA, idB, na, naT); + + //@ open NbNonce(snap, idA, idB, ?nb, ?nbT); + //@ close exists(nbT); + //@ close exists(bothIds); + labeledSecrecyLemma(lib); + //@ close NbNonce(snap, idA, idB, nb, nbT); + + a->version = 3; + //@ close Mem(a, 3); + //@ leakPkeCtxt(); +} + +bool runA(struct A* a) +//@ requires Mem(a, 1); +//@ ensures result ? Mem(a, 3) : emp; +{ + unsigned int msg1Data_len = 0; + unsigned int ciphertext1_len = 0; + unsigned int msg2Data_len = 0; + unsigned int ciphertext2_len = 0; + unsigned int msg3Data_len = 0; + unsigned int ciphertext3_len = 0; + + //@ open Mem(a, 1); + struct LabeledLib *lib = a->labeledLib; + //@ assert LabeledLibMem(lib, ?snap0, ?idA, pkePred); + //@ int idB = a->idB; + + // create nonce na + //@ Label both_label = Readers(cons(idA, cons(idB, nil))); + //@ list eventTypes = cons(INITIATE, cons(FINISHI, nil)); + //@ close exists(eventTypes); + //@ close exists(both_label); + char *na = labeledCreateNonce(lib); + if(na == 0) { + abort(); + } + //@ assert chars(na, NonceLength, ?abs_na); + //@ Term naT = nonce(abs_na, both_label); + //@ assert LabeledLibMem(lib, ?snap1, idA, pkePred) &*& a->skAT |-> ?skAT &*& a->skBT |-> ?skBT; + + // apply monotonicity snap0 -> snap1: + //@ getPkeCtxt(); + //@ isSecretKeyMonotonic(snap0, snap1, idA, skAT, 1, pkePred); + //@ isPublicKeyMonotonic(snap0, snap1, idB, skBT, 1, pkePred); + + //@ ParameterizedEvent initEv = newEvent(INITIATE, InitiateParams(idA, idB, naT)); + //@ close exists(initEv); + //@ open eventUniquePointer(eventTypes, naT); + //@ close event_pred(idA, initEv, snap1); + labeledTriggerEvent(lib); + //@ assert LabeledLibMem(lib, ?snap2, idA, pkePred); + + // apply monotonicity snap1 -> snap2: + //@ isSecretKeyMonotonic(snap1, snap2, idA, skAT, 1, pkePred); + //@ isPublicKeyMonotonic(snap1, snap2, idB, skBT, 1, pkePred); + //@ nonceOccursMonotonic(snap1, snap2, naT, both_label); + + struct Msg1 *msg1 = malloc(sizeof(struct Msg1)); + if (msg1 == 0) { + abort(); + } + msg1->Na = na; + msg1->idA = a->idA; + char *msg1Data = marshalMsg1(msg1, &msg1Data_len); + free(msg1); + if (msg1Data == 0) { + abort(); + } + //@ assert chars(msg1Data, _, ?abs_msg1); + + //@ Term msg1T = tuple3(integer(1), naT, integer(idA)); + //@ Term pkBT = publicKey(a->skBT); + //@ close exists(pkBT); + //@ close exists(msg1T); + //@ assert abs_msg1 == gamma(msg1T); + //@ char *pkB = a->pkB; + //@ assert chars(pkB, _, ?abs_pk); + //@ assert abs_pk == gamma(pkBT); + //@ Label justBLabel = Readers(cons(idB, nil)); + //@ isMsgTupleThreeCreate(snap2, integer(1), naT, integer(idA), justBLabel, pkePred); + //@ assert canEncrypt(snap2, msg1T, pkBT, pkePred) == true; + char *ciphertext1 = labeledEnc(lib, msg1Data, msg1Data_len, a->pkB, a->pkB_len, &ciphertext1_len); + //@ leak exists(msg1T); + //@ leak exists(pkBT); + free(msg1Data); + if (ciphertext1 == 0) { + abort(); + } + + //@ Term ciphertext1T = encrypt(pkBT, msg1T); + //@ close exists(ciphertext1T); + bool success = labeledSend(lib, a->idA, a->idB, ciphertext1, ciphertext1_len); + //@ assert LabeledLibMem(lib, ?snap3, idA, pkePred); + //@ leak exists(ciphertext1T); + free(ciphertext1); + if (!success){ + abort(); + } + + // apply monotonicity snap2 -> snap3: + //@ isSecretKeyMonotonic(snap2, snap3, idA, skAT, 1, pkePred); + //@ isPublicKeyMonotonic(snap2, snap3, idB, skBT, 1, pkePred); + //@ nonceOccursMonotonic(snap2, snap3, naT, both_label); + + char *ciphertext2 = labeledReceive(lib, a->idB, a->idA, &ciphertext2_len); + //@ assert LabeledLibMem(lib, ?snap4, idA, pkePred); + if (ciphertext2 == 0){ + abort(); + } + + // apply monotonicity snap3 -> snap4: + //@ isSecretKeyMonotonic(snap3, snap4, idA, skAT, 1, pkePred); + //@ isPublicKeyMonotonic(snap3, snap4, idB, skBT, 1, pkePred); + //@ nonceOccursMonotonic(snap3, snap4, naT, both_label); + + /*@ assert exists(?ciphertext2T) &*& + chars(ciphertext2, ?ciphertext2Size, ?abs_ciphertext2) &*& + gamma(ciphertext2T) == abs_ciphertext2; @*/ + //@ leak exists(ciphertext2T); + //@ close exists(ciphertext2T); + //@ close exists(skAT); + + char *skA = a->skA; + //@ assert chars(skA, _, ?abs_sk); + //@ close exists(idA); + char *msg2Data = labeledDec(lib, ciphertext2, ciphertext2_len, skA, a->skA_len, &msg2Data_len); + free(ciphertext2); + if (msg2Data == 0){ + abort(); + } + //@ assert chars(msg2Data, _, ?abs_msg2); + struct Msg2 *msg2 = unmarshalMsg2(msg2Data, msg2Data_len); + free(msg2Data); + if (msg2 == 0){ + abort(); + } + + // check na and idB: + if(memcmp(msg2->Na, na, NonceLength) != 0) { + abort(); + } + if (msg2->idB != a->idB) { + abort(); + } + + char *nb = msg2->Nb; + //@ assert chars(nb, NonceLength, ?abs_nb); + //@ Term nbT = PatternPropertyMsg2(naT, oneTerm(abs_nb), integer(idB), skAT, ciphertext2T); + free(msg2->Na); + free(msg2); + + //@ Term msg2T = tuple4(integer(2), naT, nbT, integer(idB)); + + /*@ assert [_]is_forall_t(?quantifiedExp) &*& + quantifiedExp((msgIsDecrypted)(snap4, ciphertext2T, skAT, idA, pkePred)) == true; @*/ + //@ forall_t_elim(quantifiedExp, (msgIsDecrypted)(snap4, ciphertext2T, skAT, idA, pkePred), msg2T); + //@ assert wasDecrypted(snap4, msg2T, skAT, idA, pkePred) == true; + // we no longer need the quantifier and thus leak the corresponding resource: + //@ leak [_]is_forall_t(quantifiedExp); + + //@ assert chars(skA, _, ?abs_skA); + // the following assertion is needed: + //@ assert decryptB(abs_skA, encryptB(publicKeyB(abs_skA), abs_msg2)) == abs_msg2; + + // the following 2 assertions seems to be needed: + //@ assert abs_msg2 == tuple4B(integerB(2), gamma(naT), abs_nb, integerB(idB)); + //@ assert getThirdB(abs_msg2) == abs_nb; + + //@ Label msg2Label = isPublishable(snap4, msg2T, pkePred) ? Public : Readers(cons(idA, nil)); + //@ isMsgTupleFourResolve(snap4, integer(2), naT, nbT, integer(idB), msg2Label, pkePred); + //@ close exists(nbT); + //@ close exists(msg2Label != Public); + labeledNonceOccursImpliesRandInvConditional(lib); + /*@ + if (msg2Label == Public) { + isMsgTransitive(snap4, nbT, Public, both_label, pkePred); + publishableImpliesCorruption(snap4, naT, both_label, cons(a->idA, cons(a->idB, nil)), pkePred); + assert isPublishable(snap4, nbT, pkePred) == true; + } + @*/ + + // facts after receiving msg2: + //@ bool corruptionOccured = containsCorruptId(getCorruptIds(snap4), cons(idA, cons(idB, nil))); + /*@ + if (corruptionOccured) { + assert isPublishable(snap4, nbT, pkePred) == true; + } else { + assert nonceOccurs(snap4, nbT, both_label) == true; + assert eventOccurs(snap4, idB, newEvent(RESPOND, RespondParams(idA, idB, naT, nbT))) == true; + } + @*/ + + //@ ParameterizedEvent finishI = newEvent(FINISHI, FinishIParams(idA, idB, naT, nbT)); + //@ close exists(finishI); + //@ open eventUniquePointer(cons(FINISHI, nil), naT); + //@ close event_pred(idA, finishI, snap4); + labeledTriggerEvent(lib); + //@ open eventUniquePointer(nil, naT); + //@ assert LabeledLibMem(lib, ?snap5, idA, pkePred); + + // apply monotonicity snap4 -> snap5: + //@ isSecretKeyMonotonic(snap4, snap5, idA, skAT, 1, pkePred); + //@ isPublicKeyMonotonic(snap4, snap5, idB, skBT, 1, pkePred); + //@ nonceOccursMonotonic(snap4, snap5, naT, both_label); + /*@ + if (corruptionOccured) { + isPublishableMonotonic(snap4, snap5, nbT, pkePred); + } else { + nonceOccursMonotonic(snap4, snap5, nbT, both_label); + } + @*/ + + /*@ + if (corruptionOccured) { + isMsgTransitive(snap5, nbT, Public, justBLabel, pkePred); + } else { + isMsgTransitive(snap5, nbT, both_label, justBLabel, pkePred); + } + @*/ + + struct Msg3 *msg3 = malloc(sizeof(struct Msg3)); + if (msg3 == 0) { + abort(); + } + msg3->Nb = nb; + char *msg3Data = marshalMsg3(msg3, &msg3Data_len); + free(msg3); + if (msg3Data == 0) { + abort(); + } + //@ assert chars(msg3Data, _, ?abs_msg3); + + //@ Term msg3T = tuple2(integer(3), nbT); + //@ close exists(pkBT); + //@ close exists(msg3T); + //@ isMsgTupleTwoCreate(snap5, integer(3), nbT, justBLabel, pkePred); + //@ eventOccursImpliesEventOccursFinishI(snap5, idA, idB, naT, nbT); + //@ assert canEncrypt(snap5, msg3T, pkBT, pkePred) == true; + char *ciphertext3 = labeledEnc(lib, msg3Data, msg3Data_len, a->pkB, a->pkB_len, &ciphertext3_len); + //@ leak exists(msg3T); + //@ leak exists(pkBT); + free(msg3Data); + if (ciphertext3 == 0) { + abort(); + } + + //@ Term ciphertext3T = encrypt(pkBT, msg3T); + //@ close exists(ciphertext3T); + success = labeledSend(lib, a->idA, a->idB, ciphertext3, ciphertext3_len); + //@ assert LabeledLibMem(lib, ?snap6, idA, pkePred); + //@ leak exists(ciphertext3T); + free(ciphertext3); + if (!success){ + abort(); + } + + // apply monotonicity snap5 -> snap6: + //@ isSecretKeyMonotonic(snap5, snap6, idA, skAT, 1, pkePred); + //@ isPublicKeyMonotonic(snap5, snap6, idB, skBT, 1, pkePred); + //@ nonceOccursMonotonic(snap5, snap6, naT, both_label); + //@ eventOccursMonotonic(snap5, snap6, idA, finishI); + /*@ + if (corruptionOccured) { + isPublishableMonotonic(snap5, snap6, nbT, pkePred); + } else { + nonceOccursMonotonic(snap5, snap6, nbT, both_label); + } + @*/ + + a->version = 2; + + print_hex("A has successfully finished the protocol run\nA.na", na, NonceLength); + print_hex("A.nb", nb, NonceLength); + + //@ close NaNonce(snap6, idA, idB, na, naT); + /*@ + if (corruptionOccured) { + isSuffixTransitive(snap4, snap5, snap6); + getCorruptIdsMonotonic(snap4, snap6); + subset_intersection_helper(getCorruptIds(snap4), getCorruptIds(snap6), cons(idA, cons(idB, nil))); + assert containsCorruptId(getCorruptIds(snap6), cons(idA, cons(idB, nil))) == true; + } + @*/ + //@ close NbNonce(snap6, idA, idB, nb, nbT); + //@ leakPkeCtxt(); + //@ close Mem(a, 2); + + initiatorProveSecurityProperties(a); + + return true; +} diff --git a/VeriFastPrototype/nsl/initiator.h b/VeriFastPrototype/nsl/initiator.h new file mode 100644 index 0000000..a35d9ba --- /dev/null +++ b/VeriFastPrototype/nsl/initiator.h @@ -0,0 +1,34 @@ +#ifndef INITIATOR +#define INITIATOR + +#include "labeled_library.h" + + +struct A { + struct LabeledLib *labeledLib; + int version; + int idA; + char* pkA; + unsigned int pkA_len; + char* skA; + unsigned int skA_len; + int idB; + char *pkB; + unsigned int pkB_len; + //@ Term skAT; + //@ Term skBT; +}; + +/*@ +predicate Mem(struct A* a, int version); +@*/ + +struct A *initA(struct IoLib *io_lib, struct TraceManager *tm, int initiator, int responder); +//@ requires IoLibMem(io_lib) &*& TraceManagerMem(tm, initiator, ?snap0, pkePred); +//@ ensures result != 0 ? Mem(result, 0) : emp; + +bool runA(struct A* a); +//@ requires Mem(a, 1); +//@ ensures result ? Mem(a, 3) : emp; + +#endif diff --git a/VeriFastPrototype/nsl/main.c b/VeriFastPrototype/nsl/main.c new file mode 100644 index 0000000..8bd143c --- /dev/null +++ b/VeriFastPrototype/nsl/main.c @@ -0,0 +1,273 @@ +#include +#include +#include +#include +#include "initiator.h" +#include "responder.h" + +char *keypair_1_pk = "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvVMEI16XJOvbU1WjpnP/ne2ndS7zuFMgidUxrSVB6loHpt59ZBfhczjnl0ZkSOvbGmJrbAfqIjHs9oFwmZoTp/hgoJF0bXFbJZQixx79gt3SNwt9cqUvA0851lY00dYkfiqRM/rT9e6C0VAGuIWo/VOpf4zDIrYKwC4DbDQ6e+DyuNqgym+5PnAezo5wwqA05k7TRhq5fBIDuH5LepemMwz0uJmwascsU6KNXFVMrc+e8lu1uiBeELiXH2IliOw0GgoYxx26OLM8+o/uBLoHcxiprR3nXangHsr+3I7LyIZtRyOIycok1/XVj6Nch5bzWX53+7xrSC0P4OncNPIm++O7NOqLAE2kZfAzHrNA2oBu7wUDE4BVnvM9QG0KzTIY9QVQbawNzY+SzmpolkEvJ5AfvdtGQond/vgi8+DLZwyP5CH1mJMiT1TSFlPb8C17FuYdz3mHMgBM6Z/6edVcmeul4gujFcc+umodDpZqw24wdx+t3UStOwCArX/JMbSH9Zu17RdnwiPUK8cYa756rqzScJhIReZuDZgG2jqxWcr3IvJcrllDiwyhB/pELn9OFYRsd0QdHNA16GLwRZejmxJXp5OC9UVzViJ+O4NeW+Sx0tb5o0yIbBEUHY1Kl7Z519svRrXCKASk0EfAyWCcrY38TfyvmMNwspNrh3K/pnMCAwEAAQ=="; +char *keypair_1_sk = "MIIJKAIBAAKCAgEAvVMEI16XJOvbU1WjpnP/ne2ndS7zuFMgidUxrSVB6loHpt59ZBfhczjnl0ZkSOvbGmJrbAfqIjHs9oFwmZoTp/hgoJF0bXFbJZQixx79gt3SNwt9cqUvA0851lY00dYkfiqRM/rT9e6C0VAGuIWo/VOpf4zDIrYKwC4DbDQ6e+DyuNqgym+5PnAezo5wwqA05k7TRhq5fBIDuH5LepemMwz0uJmwascsU6KNXFVMrc+e8lu1uiBeELiXH2IliOw0GgoYxx26OLM8+o/uBLoHcxiprR3nXangHsr+3I7LyIZtRyOIycok1/XVj6Nch5bzWX53+7xrSC0P4OncNPIm++O7NOqLAE2kZfAzHrNA2oBu7wUDE4BVnvM9QG0KzTIY9QVQbawNzY+SzmpolkEvJ5AfvdtGQond/vgi8+DLZwyP5CH1mJMiT1TSFlPb8C17FuYdz3mHMgBM6Z/6edVcmeul4gujFcc+umodDpZqw24wdx+t3UStOwCArX/JMbSH9Zu17RdnwiPUK8cYa756rqzScJhIReZuDZgG2jqxWcr3IvJcrllDiwyhB/pELn9OFYRsd0QdHNA16GLwRZejmxJXp5OC9UVzViJ+O4NeW+Sx0tb5o0yIbBEUHY1Kl7Z519svRrXCKASk0EfAyWCcrY38TfyvmMNwspNrh3K/pnMCAwEAAQKCAgBVrHCN9OseySCqOHHjDFEbTYVfEQ03V169IN3nBZori+w0hjBmECx0sMaUfUU6fojbCrij3X0FVmRuNKsYx1GnzE0lvEzcjdR6T+vhAdQk2W6cfDWboMaCj+KTbNVgM7C161tkE1jBzNokEDvKWqnbYXWtg6x2U7zPtMLVv1jL4ELWhhEHKsHAUIqQXIMIf+kQY5FWAxf23kwSvAw6ANAz/+PqeZoM5+7WNhQUOYGGkhLSh8/X13fZxz6T9B0aNhFpyzHlQT2ZFPs+Q82pE+n1Gq8F6SdfClWieagVdQUgzDw9WgY3kqNTmyq2Ym2n6hZbZFC1eVFvCv1JgWqmBwK+Z/slaVhROUZ5/AMB0Dhd8v5aYp5d7o+xMClbCB+hHvzGFXTem+wm9pIGAqXZUPE3jWEz2xf6QsQ+NypubuZRUrxzcxOM1k+N7xRLc17le05vusqWT/l+InBfZEhYRaTo2BduvWZNX5O3B3IlMCdPRYPY8ux+rH0MYzSVLmhtrURX526nXJkVfdMEj0iltO2K+bdfAleiFXs1AY+BcE3asINvxBm3QqpjG5Pyx/H+Ahsl4BwrSP+jUlJDQZnsLD9RDsgRxAVg9Qgn5Et4JsnjScAtiQvsRE832ZDqxgQcVT/lBLZ0ghOVcI1bMCKQ0mIxWOBp7CzO5I2eXE6ePRkzoQKCAQEAyNTKK7ZprS8XJpSHxf2VY6b8vn6RVlZtuhfITSO0IqJVgtDUWttHuN4s8IJ8Go7aCDuuKbP0xnha7ssEZMNjcwutiZXPkvOflDvDq/dHIkuCa4w7HIP28a/P4naaS9A4whUjPM4+jtHkk4EWK/8oifsQHNg6NEBJs7wI27vQ2Pq0J4Jn8VXQ7MbLl7J+Lqdp6yFxVq8IYudJXJudnCQmW+lAAy+8LpICcOjzeuXdrmsuZh7J8qO+CaspCBL2G2QSMEbvyDLL0P8D/05fBTyI8lrM5n4SoxNHw8IsdD7X3wiJBMid09ot8/ikPPzfeJVqEOh9NP9lxyomMA/PgTJqUQKCAQEA8VUEB1m04rP3JHRR7dbtjf7/iNS60RB3lYnz9tNqLcng3GAgPwU226kVsSUf+H3129tQpabwm9CZsRnxmLNOWkEZAATWwLL2KrmAn8+FkqzC5lzYeUr2Pj/b3nCgq5vYCukjhOxzhYRaM06dAjOa/OdfnYrg4wpvWeXQCvDU3bWpJMyPIAqElyXwbHfL3bW7Cf2BTw8bX4UNnW5exyU8oeMcw6PdxBilJ5m+eL/msWdr/SopVUgPsqp/7ZBxuiTrZkfsdRhfkvwhEbIC2ehRGSMJ5kjHuUAAbd+bkExf1UPvixdUWCw5zYaZXMfBXLdKTHSMOmAuAGxU3hjkr5CPgwKCAQAv+eVyG9mS7bTyGnl06udNLw8h0sqVfYAo/JV1GBpoS69x2MFiExBHMYw6yHEtRwL/BILOo5bN8uKGOSmLiMGxMhD61TcJO/nbR4uvARuVLcSyPIXCgiP0CLP4vayOf+ePNc19MSfwpmOceTH6wLHGhJuMyHrfEJyKu1jCZVO3Ae0XoyeBl6aZacQpMRLDwmqjKRISSy4NsoLsBKDaNCiVvFr2Z+jklyzOHFhN+6vBhwlGjARiWouDc8gRjbYNRRKzRb7ybHAUNVeXHfnFHnj4rIhWZ7e8DVcPhMtp2bloJnnVErfhbDWeGr/hcedQvyDfeSqwBnMh6QfGY4CGtKyBAoIBAQC5ek6zW4XDau96TBAfzL0dEivRPTYrsg1GmBUx0cDcWjkBBrwh018bKfPrBw5wTFbmV8O+3PQ1vPgyfi8J3l1MzpVpR07KIYFCyvmJWdReK3tL03Xomu1wYGIartM9sXQ0xoQvCA+tQVCV+EiBxanL0APTsEYxGPcFz7O6hOgFUjYiezlRNeQ7ysPiiZvc1WxgPD7ixUiTfE4/ffFH+12DSmr0DgBGU26zZd1XLp4eIM+Fbp7/1XeDKLlTm11c5D0rigG46TejXzYHRJoeYgfaWuyj2bHutbretyn3mEtbPHBhpVeEwNDYHifGgBwjpxdqdXTE9ODGIHyFifpQ2LkDAoIBACFaCJXJpoShj2sBfwqvYMMyQLlHpSqEDu6Q1TlNqhIevyTIAQfpxM84FV0J77hVUU7r9KBg6qqkvKYoUZhBTqzpGtCpt0vBAKHIFfs/Cg17B/9F1938YypkDkpLfY28AekmG2ZuPAUlJX/hXsMMs+iAVvb96B48vSyJn3kiRYF5Sqixk9IBtJc0m7hPVEvCa3q/PXYIL4+klgtbxttd+Ln0uiBGj5KXMpYLxfWg9ZRqAfqGpss4+s+A2FWkY6uozhznCGuqyCjcC0GRL1suqpZqLbZfNam9HnkYjdIe09ZALF5RhWa9anBpfJD2/G34pElZRhvhDHH7quwkRMVlbWM="; + +char *keypair_2_pk = "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAx+KThX2pDDgGcNzmDxBfldrN+v7sWoW3FDtszT0Bb/pqVh5Wa2FI8S+eO3SJgytj2B3Kj9GjGy9UqJNTBAJCcm/xkEVCvVQiSro0id+hTN7zSkIDYiUs01LxjF9eZ71715jeXLiNWQNBTaiLrRuvvhQeRSQ328UzKj5aGklgUxmI/9VWOngmELYdsPET/FdxkM0481a+G4o5D7wRZLxAk/EbeNB0ifi4VB4sysMFGX06330KtmJq3jH/jKIOLxu1+OwLT8CAPM/t/J3Vgerr9KBcnEmyIt91+1OJOAUhQGRx0VOnU3qR3WPRUaDrL/5XeozSd45wK2dFZlLcPQ6CSizVPe0lPNobwiwJ/GS3m5YT2EUlOvKdqRMK8n9I3stsaC3sVcFQRIwPmWqg5B7y4QO1vQfBMyz8rc1kxfwETo5so3IGPzxKQWkF4Az2wdKyljuRApAK5DgF4q7mLiCeGXofuBFp1KqWV5QLUKBxNKy5qCmbv+FarCG9f0X/vaCLMzX2qzwnGlMLHsXY5rwzmchtvS57qL992hYh/G0BPjO5mr+m87vJuZhX+4dVAc8h7XZZbOTaORVRXKnh7qJ4N0czF/e4bH0Qv6iaMWTEEKLjKmt8N09Zw3AVD9tD7feIgI6Rj9BfCItIcOYS9wYVRMuJBDtssFEXdmyBRGUe6ykCAwEAAQ=="; +char *keypair_2_sk = "MIIJKAIBAAKCAgEAx+KThX2pDDgGcNzmDxBfldrN+v7sWoW3FDtszT0Bb/pqVh5Wa2FI8S+eO3SJgytj2B3Kj9GjGy9UqJNTBAJCcm/xkEVCvVQiSro0id+hTN7zSkIDYiUs01LxjF9eZ71715jeXLiNWQNBTaiLrRuvvhQeRSQ328UzKj5aGklgUxmI/9VWOngmELYdsPET/FdxkM0481a+G4o5D7wRZLxAk/EbeNB0ifi4VB4sysMFGX06330KtmJq3jH/jKIOLxu1+OwLT8CAPM/t/J3Vgerr9KBcnEmyIt91+1OJOAUhQGRx0VOnU3qR3WPRUaDrL/5XeozSd45wK2dFZlLcPQ6CSizVPe0lPNobwiwJ/GS3m5YT2EUlOvKdqRMK8n9I3stsaC3sVcFQRIwPmWqg5B7y4QO1vQfBMyz8rc1kxfwETo5so3IGPzxKQWkF4Az2wdKyljuRApAK5DgF4q7mLiCeGXofuBFp1KqWV5QLUKBxNKy5qCmbv+FarCG9f0X/vaCLMzX2qzwnGlMLHsXY5rwzmchtvS57qL992hYh/G0BPjO5mr+m87vJuZhX+4dVAc8h7XZZbOTaORVRXKnh7qJ4N0czF/e4bH0Qv6iaMWTEEKLjKmt8N09Zw3AVD9tD7feIgI6Rj9BfCItIcOYS9wYVRMuJBDtssFEXdmyBRGUe6ykCAwEAAQKCAgAItrbppd29u5+EQg0BcRxJox1BqOVS2OtvRVvr4pHyeL8z++SWj8onQYUrYFwyTKzwmfPfqyrqH3kYVDvVO+f7pyenu206ZbWM/msV65rTiBChFxmgqLA4kjAXh3zNFvSUJITlE+KNk9e/8+4K4N8bcTMUnoyU1xbw64DwDmzVkpJnXLyClgRKzDWlJg1R/dnkjx3BdfGZr7/nELLNamuCR4uEC8pYzW/zstEPEctiHhR387KI2ud/wjw6vEHUCeg37spbcq/kdsgQMC1DmaMpqef/pDLQ1F8HUs/zkj2t2fMgRf5QZFKBjT1tyo8WK4dSv1rZfWskaT8cQ9Z4Du7dCx728uIgBu2C9xVjipGo6LlyJ+LCaq4Yo3TZTxQhNfVA6+LQD5MFpBjxeaCms0wGYhYtJQ2GkYO2ImSH/52f8r6KeHCMolcESUsuv5feewX76jQMkqYzX6GnEfkWlcleTk5hFRRom42LgWdufzbhhyHf5EqAFdSb9Umf7fUR1CMxnCJzAiE48ObTNMmNyiiAX5vfuwR1GeOf9KJpOULh3g1ilcZEzpCJRqqPktZC8+yeFD2f6GZQlrtM6Gpi2ke6F3b0ewYCW8B3TM+XensWNj+p+DiZpyCdW3q9cug2dtrf/54fKSFXp1sOsdtvh6o/LX9upLi9blxqIvDd6DvfpQKCAQEAzK2zJbgN5ip5ZqH1IFmyVhf/n/owNMffFZt+nMjy4sro9vto3NxlbaSkirX2x7znGutLSwGkq48QydxohpYMHeDs+0Bb2uVqQAgsQtb533YsQtLoCq75olH2WD1KU7rKFCsEzk1P/uroYdzZd5ELI/6I6JUXbYpL8ddYjxWOIIokldZE2nl2qyNfXbE6+D/YY4m4343kXCQXRnE8SrGmt2oV3IS7y1vcXq9zUEP6JtcPjz+9TzaZyjWfiyk6r5jl7KyeeUw3Y2dD0q+/WZ3A5MQw5VUOB1/0KYX/ZFijkAilKS986XAB7OVLMTC62ggSBE5rHrqkSbAEv95jSC756wKCAQEA+gEvZWR/81tLw6wSYb01HEoU61YGZrGPbcF2OJH+5aHdvB4ATUhN8uhDDFmmHBi0RtNLXbUbXDedASSwmTd17dvMxvecsUNFtEiyjh+YYTqJrMLfaLS2XEOV/kums4i2NCn655TLpAokHK6b1vF6n/33AbrolF4mIfVGYeFVeUgi2MBUxGbYa/sXs7aJ1MWPvks19XyWCBF2za+wH8rgQBLjrKrQMS61EHRzTTauwMsAO+SJgB03cAkLMOKGMNOiyXOSrLROCB2EUr6aW+wYmxjFvjG+8e5538YWf+jtencWwnisjRrcnwpYQEvvA3sRSf8te7tDN1s2l9kDmgt2OwKCAQAtboTsY21aYKUv3dU+SAqox4zrIqqenJrs/eXdwVEAfE+3uths5dLxwnDvhTJw5YJa3E2LKaM2nXv5gp4E+btYyntvzbpV3UR3UBkbAQLX0jBC6POuo2Yv3IeU0I32BekjDuVzMYAHMndAebgfrSdO5wnWrnlTzDXNSaKTqBIzMNasF7KS2BE6LZDWiCdxwSIz/fb2UFWXCj/MWAgtAD/kSHzvxNq2af6BWep7r4sQIf6HKnvH4HPEiaCPUCiBn6uxnCNVA1DsFJjeZDpSFw0g+ldsIDQL+QWGTgMBcBdmOjUG7k6Itl8HCWJmWc2v5ciyAgIPARjEbnivahqZhCvfAoIBADxA2t5x+VB6mWkAaLG7uzglNqN9aS+I7cuDC+4YabmIaHt1M5dsrLS1e6tXU+yDm7dSJ2DfTEfOc32aDSHwNvDrv4/Yj6A9WWhY+Qe936jXReUoVlXS7/yOoXDXZMbyVQ9/aqQzvVy8wPVUs+R68JXszIJTPMi9ZC1dAuiGOWZwl01sFUH8k3561ryOauun7bvsPoX6z+ID64EpLaaL674lj0/HH0QrQKJFnqBmZHm8s0K8EtOYtwq+cz8F6VeNOjeZLimHjyLvkjurCmLLJScEMmxjauS+GAtxn2yWg923I/ocwWGErtV51ckxQ9qv53vRD3I5sLp/tkmkmPSgfI8CggEBALJwwX99YSeg35QGeiHjfpzHPeodTnuYaoigKR53PCkOgPmadi8VrxQZUU0HyjEko8RqsEjxuY0X2XRbaslIJQIL6JHun6ImBnun35TrCMCI/B/XjL+NsGnZVp1lNp1FeogmBRi63VtMFi6JvUbMhjjYMxB3wRyYxsY+1dEezv6iQzQanhBL+upOp1/PAymlAyW0Z/fZSX7urpWuefxXcKNnxm4e7moDITpTjGFkzvNVctBcIyVHnEhoi9WvTONumJKnV5OXn26ert1fx55lhENWyRPyz1wijtMtvE4sH3buCtN6vCHmNK/l45G03+jvP129TT2pN4g1TWmSSth46Rk="; + +struct Cli_Options { + bool hasIsInitiator; + bool isInitiator; + bool hasAddress; + char *address; // string containing an IPv4 address + bool hasPort; + int port; + bool hasSk; + char *sk; + bool hasPk; + char *pk; +}; + +void parsePort(struct Cli_Options *options) { + options->port = atoi(optarg); + options->hasPort = true; +} + +bool parse(struct Cli_Options *options, int argc, char *argv[]) { + const char *short_opt = "ia:p:s:k:"; + struct option long_opt[] = + { + {"isInitiator", no_argument, NULL, 'i'}, + {"address", required_argument, NULL, 'a'}, + {"port", required_argument, NULL, 'p'}, + {"privateKey", required_argument, NULL, 's'}, + {"peerPublicKey", required_argument, NULL, 'k'}, + {NULL, 0, NULL, 0} + }; + + int longindex; + int c; + while((c = getopt_long(argc, argv, short_opt, long_opt, &longindex)) != -1) { + switch (c) { + case -1: // no more options + break; + case 0: // long options + switch (longindex) { + case 2: + parsePort(options); + break; + default: + return false; + } + break; + case 'i': + options->isInitiator = true; + options->hasIsInitiator = true; + break; + case 'a': + options->address = optarg; + options->hasAddress = true; + break; + case 'p': + parsePort(options); + break; + case 's': + options->sk = optarg; + options->hasSk = true; + break; + case 'k': + options->pk = optarg; + options->hasPk = true; + break; + default: + return false; + } + } + + if (!options->hasIsInitiator) { + options->isInitiator = false; + options->hasIsInitiator = true; + } + + return options->hasIsInitiator && options->hasAddress && options->hasPort && options->hasSk && options->hasPk; +} + +int main(int argc, char *argv[]) { + struct Cli_Options options; + bool success = parse(&options, argc, argv); + if (!success) { + return 1; + } + + const int initiatorId = 0; + const int responderId = 1; + struct TraceManager *tm = create_trace_managers(2); + if (tm == NULL) { + return 1; + } + + struct IoLib *io_lib = createIoLib(options.isInitiator, options.isInitiator ? responderId : initiatorId, options.address, options.port); + if (io_lib == NULL) { + free(tm); + return 1; + } + + if (options.isInitiator) { + struct A *a = malloc(sizeof(struct A)); + if (a == NULL) { + IoLib_free(io_lib); + free(tm); + return 1; + } + + a->labeledLib = createLabeledLib(io_lib, tm, initiatorId); + if(a->labeledLib == 0) { + free(a); + IoLib_free(io_lib); + free(tm); + return 1; + } + + a->version = 1; + a->idA = initiatorId; + a->idB = responderId; + + unsigned int skA_len; + char *skA = parsePKCS1PrivateKey(options.sk, &skA_len); + if (skA == NULL) { + free(a->labeledLib); + free(a); + IoLib_free(io_lib); + free(tm); + return 1; + } + + unsigned int pkA_len; + char *pkA = getPublicKey(skA, skA_len, &pkA_len); + if (pkA == NULL) { + free(skA); + free(a->labeledLib); + free(a); + IoLib_free(io_lib); + free(tm); + return 1; + } + + a->skA = skA; + a->skA_len = skA_len; + a->pkA = pkA; + a->pkA_len = pkA_len; + + unsigned int pkB_len; + char *pkB = parsePKIXPublicKey(options.pk, &pkB_len); + if (pkB == NULL) { + free(pkA); + free(skA); + free(a->labeledLib); + free(a); + IoLib_free(io_lib); + free(tm); + return 1; + } + + a->pkB = pkB; + a->pkB_len = pkB_len; + + bool success = runA(a); + if (!success) { + free(pkB); + free(pkA); + free(skA); + free(a->labeledLib); + free(a); + IoLib_free(io_lib); + free(tm); + return 1; + } + + free(pkB); + free(pkA); + free(skA); + free(a->labeledLib); + free(a); + } else { + struct B *b = malloc(sizeof(struct B)); + if (b == NULL) { + IoLib_free(io_lib); + free(tm); + return 1; + } + + b->labeledLib = createLabeledLib(io_lib, tm, responderId); + if(b->labeledLib == 0) { + free(b); + IoLib_free(io_lib); + free(tm); + return 1; + } + + b->version = 1; + b->idA = initiatorId; + b->idB = responderId; + + unsigned int skB_len; + char *skB = parsePKCS1PrivateKey(options.sk, &skB_len); + if (skB == NULL) { + free(b->labeledLib); + free(b); + IoLib_free(io_lib); + free(tm); + return 1; + } + + unsigned int pkB_len; + char *pkB = getPublicKey(skB, skB_len, &pkB_len); + if (pkB == NULL) { + free(skB); + free(b->labeledLib); + free(b); + IoLib_free(io_lib); + free(tm); + return 1; + } + + b->skB = skB; + b->skB_len = skB_len; + b->pkB = pkB; + b->pkB_len = pkB_len; + + unsigned int pkA_len; + char *pkA = parsePKIXPublicKey(options.pk, &pkA_len); + if (pkA == NULL) { + free(pkB); + free(skB); + free(b->labeledLib); + free(b); + IoLib_free(io_lib); + free(tm); + return 1; + } + + b->pkA = pkA; + b->pkA_len = pkA_len; + + bool success = runB(b); + if (!success) { + free(pkA); + free(pkB); + free(skB); + free(b->labeledLib); + free(b); + IoLib_free(io_lib); + free(tm); + return 1; + } + + free(pkA); + free(pkB); + free(skB); + free(b->labeledLib); + free(b); + } + + IoLib_free(io_lib); + free(tm); + + return 0; +} diff --git a/VeriFastPrototype/nsl/messages.c b/VeriFastPrototype/nsl/messages.c new file mode 100644 index 0000000..e509f87 --- /dev/null +++ b/VeriFastPrototype/nsl/messages.c @@ -0,0 +1,154 @@ +#include "messages.h" +#include +#include +#include +#include + + +char *marshalMsg1(struct Msg1* msg, unsigned int *msg_len) +{ + *msg_len = sizeof(uint32_t) + NonceLength + sizeof(uint32_t); + char *buf = malloc(*msg_len); + if (buf == NULL) { + return NULL; + } + + *(uint32_t *)buf = htonl(1); + memcpy(buf + sizeof(uint32_t), msg->Na, NonceLength); + *(uint32_t *)(buf + sizeof(uint32_t) + NonceLength) = htonl((uint32_t)msg->idA); + + return buf; +} + +struct Msg1 *unmarshalMsg1(char *packet, unsigned int msg_len) +{ + if (msg_len < sizeof(uint32_t) + NonceLength + sizeof(uint32_t)) { + return NULL; + } + + uint32_t tag = ntohl(*(uint32_t *)packet); + if (tag != 1) { + return NULL; + } + + char *na = malloc(NonceLength); + if (na == NULL) { + return NULL; + } + + memcpy(na, packet + sizeof(uint32_t), NonceLength); + + int idA = (int)ntohl(*(uint32_t *)(packet + sizeof(uint32_t) + NonceLength)); + + struct Msg1 *msg1 = malloc(sizeof(struct Msg1)); + if (msg1 == NULL) { + free(na); + return NULL; + } + + msg1->Na = na; + msg1->idA = idA; + + return msg1; +} + +char *marshalMsg2(struct Msg2* msg, unsigned int *msg_len) +{ + *msg_len = sizeof(uint32_t) + 2 * NonceLength + sizeof(uint32_t); + char *buf = malloc(*msg_len); + if (buf == NULL) { + return NULL; + } + + *(uint32_t *)buf = htonl(2); + memcpy(buf + sizeof(uint32_t), msg->Na, NonceLength); + memcpy(buf + sizeof(uint32_t) + NonceLength, msg->Nb, NonceLength); + *(uint32_t *)(buf + sizeof(uint32_t) + 2 * NonceLength) = htonl((uint32_t)msg->idB); + + return buf; +} + +struct Msg2 *unmarshalMsg2(char *packet, unsigned int msg_len) +{ + if (msg_len < sizeof(uint32_t) + 2 * NonceLength + sizeof(int)) { + return NULL; + } + + uint32_t tag = ntohl(*(uint32_t *)packet); + if (tag != 2) { + return NULL; + } + + char *na = malloc(NonceLength); + if (na == NULL) { + return NULL; + } + + memcpy(na, packet + sizeof(uint32_t), NonceLength); + + char *nb = malloc(NonceLength); + if (nb == NULL) { + free(na); + return NULL; + } + + memcpy(nb, packet + sizeof(uint32_t) + NonceLength, NonceLength); + + int idB = (int)ntohl(*(uint32_t *)(packet + sizeof(uint32_t) + 2 * NonceLength)); + + struct Msg2 *msg2 = malloc(sizeof(struct Msg2)); + if (msg2 == NULL) { + free(nb); + free(na); + return NULL; + } + + msg2->Na = na; + msg2->Nb = nb; + msg2->idB = idB; + + return msg2; +} + +char *marshalMsg3(struct Msg3* msg, unsigned int *msg_len) +{ + *msg_len = sizeof(uint32_t) + NonceLength; + char *buf = malloc(*msg_len); + if (buf == NULL) { + return NULL; + } + + *(uint32_t *)buf = htonl(3); + memcpy(buf + sizeof(uint32_t), msg->Nb, NonceLength); + + return buf; +} + +struct Msg3 *unmarshalMsg3(char *packet, unsigned int msg_len) +{ + if (msg_len < sizeof(uint32_t) + NonceLength) { + return NULL; + } + + uint32_t tag = ntohl(*(uint32_t *)packet); + if (tag != 3) { + return NULL; + } + + char *nb = malloc(NonceLength); + if (nb == NULL) { + return NULL; + } + + memcpy(nb, packet + sizeof(uint32_t), NonceLength); + + struct Msg3 *msg3 = malloc(sizeof(struct Msg3)); + if (msg3 == NULL) { + free(nb); + return NULL; + } + + msg3->Nb = nb; + + return msg3; +} diff --git a/VeriFastPrototype/nsl/messages.h b/VeriFastPrototype/nsl/messages.h new file mode 100644 index 0000000..1089b58 --- /dev/null +++ b/VeriFastPrototype/nsl/messages.h @@ -0,0 +1,95 @@ +#ifndef MESSAGES +#define MESSAGES + +#include "crypto.h" + + +struct Msg1 { + char* Na; + int idA; +}; + +struct Msg2 { + char* Na; + char* Nb; + int idB; +}; + +struct Msg3 { + char* Nb; +}; + +char *marshalMsg1(struct Msg1* msg, unsigned int *msg_len); +/*@ requires [?f]msg->Na |-> ?NaT &*& chars(NaT, NonceLength, ?msgBytes) &*& + *msg_len |-> _ &*& + [f]msg->idA |-> ?id; @*/ +/*@ ensures [f]msg->Na |-> NaT &*& chars(NaT, NonceLength, msgBytes) &*& + *msg_len |-> ?res_len &*& + [f]msg->idA |-> id &*& + (result != 0 ? + chars(result, res_len, ?resultBytes) &*& + malloc_block_chars(result, res_len) &*& + res_len >= 4 + NonceLength &*& + resultBytes == tuple3B(integerB(1), msgBytes, integerB(id)) + : true); @*/ + +struct Msg1 *unmarshalMsg1(char *packet, unsigned int msg_len); +//@ requires [?f]chars(packet, msg_len, ?packetBytes); +/*@ ensures [f]chars(packet, msg_len, packetBytes) &*& + (result != 0 ? + result->Na |-> ?NaT &*& chars(NaT, NonceLength, ?msgBytes) &*& malloc_block_chars(NaT, NonceLength) &*& + result->idA |-> ?id &*& + malloc_block_Msg1(result) &*& + packetBytes == tuple3B(integerB(1), msgBytes, integerB(id)) + : emp); + @*/ + +char *marshalMsg2(struct Msg2* msg, unsigned int *msg_len); +/*@ requires [?f]msg->Na |-> ?NaT &*& chars(NaT, NonceLength, ?naBytes) &*& + [f]msg->Nb |-> ?NbT &*& chars(NbT, NonceLength, ?nbBytes) &*& + *msg_len |-> _ &*& + [f]msg->idB |-> ?id; @*/ +/*@ ensures [f]msg->Na |-> NaT &*& chars(NaT, NonceLength, naBytes) &*& + [f]msg->Nb |-> NbT &*& chars(NbT, NonceLength, nbBytes) &*& + *msg_len |-> ?res_len &*& + [f]msg->idB |-> id &*& + (result != 0 ? + chars(result, res_len, ?resultBytes) &*& + malloc_block_chars(result, res_len) &*& + res_len >= 4 + 2*NonceLength &*& + resultBytes == tuple4B(integerB(2), naBytes, nbBytes, integerB(id)) + : true); @*/ + +struct Msg2 *unmarshalMsg2(char *packet, unsigned int msg_len); +//@ requires [?f]chars(packet, msg_len, ?packetBytes); +/*@ ensures [f]chars(packet, msg_len, packetBytes) &*& + (result != 0 ? + result->Na |-> ?Na &*& chars(Na, NonceLength, ?naBytes) &*& malloc_block(Na, NonceLength) &*& + result->Nb |-> ?Nb &*& chars(Nb, NonceLength, ?nbBytes) &*& malloc_block(Nb, NonceLength) &*& + result->idB |-> ?id &*& + malloc_block_Msg2(result) &*& + packetBytes == tuple4B(integerB(2), naBytes, nbBytes, integerB(id)) + : emp); @*/ + +char *marshalMsg3(struct Msg3* msg, unsigned int *msg_len); +/*@ requires [?f]msg->Nb |-> ?Nb &*& [?f2]chars(Nb, NonceLength, ?msgBytes) &*& + *msg_len |-> _; @*/ +/*@ ensures [f]msg->Nb |-> Nb &*& [f2]chars(Nb, NonceLength, msgBytes) &*& + *msg_len |-> ?res_len &*& + (result != 0 ? + chars(result, res_len, ?resultBytes) &*& + malloc_block_chars(result, res_len) &*& + res_len >= 4 + NonceLength &*& + resultBytes == tuple2B(integerB(3), msgBytes) + : true); @*/ + +struct Msg3 *unmarshalMsg3(char *packet, unsigned int msg_len); +//@ requires [?f]chars(packet, msg_len, ?packetBytes); +/*@ ensures [f]chars(packet, msg_len, packetBytes) &*& + (result != 0 ? + result->Nb |-> ?Nb &*& chars(Nb, NonceLength, ?msgBytes) &*& malloc_block(Nb, NonceLength) &*& + malloc_block_Msg3(result) &*& + packetBytes == tuple2B(integerB(3), msgBytes) + : emp); @*/ + +#endif diff --git a/VeriFastPrototype/nsl/nsl_event_types.gh b/VeriFastPrototype/nsl/nsl_event_types.gh new file mode 100644 index 0000000..57665d5 --- /dev/null +++ b/VeriFastPrototype/nsl/nsl_event_types.gh @@ -0,0 +1,11 @@ +#ifndef NSL_EVENT_TYPES +#define NSL_EVENT_TYPES + + +// event types for the 4 NSL events: +#define INITIATE 1 +#define RESPOND 2 +#define FINISHI 3 +#define FINISHR 4 + +#endif diff --git a/VeriFastPrototype/nsl/nsl_pke_pred.c b/VeriFastPrototype/nsl/nsl_pke_pred.c new file mode 100644 index 0000000..e6c54f5 --- /dev/null +++ b/VeriFastPrototype/nsl/nsl_pke_pred.c @@ -0,0 +1,146 @@ +//@ #include "nsl_pke_pred.gh" + + +/*@ +// we give `PkeCtxt` here a body: +predicate PkeCtxt(PkePred p) = + p == pkePred; + +lemma void getPkeCtxt() + requires true; + ensures PkeCtxt(pkePred); +{ + close PkeCtxt(pkePred); +} + +lemma void leakPkeCtxt() + requires PkeCtxt(pkePred); + ensures true; +{ + open PkeCtxt(pkePred); +} + +lemma void learnPkeCtxt(PkePred p) + requires PkeCtxt(p); + ensures PkeCtxt(p) &*& p == pkePred; +{ + open PkeCtxt(p); + getPkeCtxt(); +} + +lemma void eventOccursImpliesEventOccursFinishI(Trace snap, int idA, int idB, Term naT, Term nbT) + requires eventOccurs(snap, idA, newEvent(FINISHI, FinishIParams(idA, idB, naT, nbT))) == true; + ensures eventOccursFinishI(snap, idB, nbT) == true; +{ + switch(snap) { + case root(root_terms): + case makeEvent(t0, p, e): + switch(e) { + case newEvent(type, params): + if (!eventOccursFinishIHelper(params, idB, nbT, p) || type != FINISHI) { + eventOccursImpliesEventOccursFinishI(t0, idA, idB, naT, nbT); + } + } + case makeCorrupt(t0, id): eventOccursImpliesEventOccursFinishI(t0, idA, idB, naT, nbT); + case makeMessage(t0, to, from, term): eventOccursImpliesEventOccursFinishI(t0, idA, idB, naT, nbT); + case makeDropMessage(t0, to, from, term): eventOccursImpliesEventOccursFinishI(t0, idA, idB, naT, nbT); + case makeNonce(t0, term): eventOccursImpliesEventOccursFinishI(t0, idA, idB, naT, nbT); + case makePublic(t0, term): eventOccursImpliesEventOccursFinishI(t0, idA, idB, naT, nbT); + } +} + +lemma void eventOccursFinishIMonotonic(trace tr,trace tr2, int b, Term nb) + requires isSuffix(tr, tr2) == true && eventOccursFinishI(tr, b, nb); + ensures eventOccursFinishI(tr2, b, nb) == true; +{ + switch (tr2) { + case root(root_terms): + case makeEvent(t0, pr, e): + switch (e) { + case newEvent(type, params): + if (type == FINISHI && eventOccursFinishIHelper(params, b, nb, pr)) { + assert eventOccursFinishI(tr2, b, nb) == true; + } else if(tr2 != tr) { + eventOccursFinishIMonotonic(tr, t0, b, nb); + } + }; + case makeCorrupt(t0, id): + if (tr2 != tr) { + eventOccursFinishIMonotonic(tr, t0, b, nb); + } + case makeMessage(t0,to,from,term): + if (tr2 != tr) { + eventOccursFinishIMonotonic(tr, t0, b, nb); + } + case makeDropMessage(t0, to, from, term): + if (tr2 != tr) { + eventOccursFinishIMonotonic(tr, t0, b, nb); + } + case makeNonce(t0, term): + if (tr2 != tr) { + eventOccursFinishIMonotonic(tr, t0, b, nb); + } + case makePublic(t0, term): + if (tr2 != tr) { + eventOccursFinishIMonotonic(tr, t0, b, nb); + } + } +} + +lemma void ppredMonotonic(Trace t1, Trace t2, Term plaintext, Term pk, int skOwner) + requires isSuffix(t1, t2) == true && ppred(t1, plaintext, pk, skOwner) == true; + ensures ppred(t2, plaintext, pk, skOwner) == true; +{ + switch (plaintext) { + case integer(value): + case stringTerm(str): + case encrypt(enc, pt): + case hash(term): + case publicKey(str_k): + case nonce(bytes, l): + case tuple2(term1, term2): + if (isMsg3(plaintext, pk)) { + eventOccursFinishIMonotonic(t1, t2, skOwner, term2); + } + case tuple3(term1, term2, term3): + if (isMsg1(plaintext, pk)) { + nonceOccursMonotonic(t1, t2, term2, Readers(cons(getInteger(term3), cons(skOwner, nil)))); + eventOccursMonotonic(t1, t2, getInteger(term3), newEvent(INITIATE, InitiateParams(getInteger(term3), skOwner, term2))); + } + case tuple4(term1, term2, term3, term4): + if (isMsg2(plaintext, pk)) { + nonceOccursMonotonic(t1, t2, term3, Readers(cons(skOwner, cons(getInteger(term4), nil)))) ; + eventOccursMonotonic(t1, t2, getInteger(term4), newEvent(RESPOND, RespondParams(skOwner, getInteger(term4), term2, term3))); + } + } +} + +lemma void pkePredHelperMonotonic(Trace t1, trace t2, Term plaintext, Term pk, Label l) +requires isSuffix(t1, t2) == true && pkePredHelper(t1, plaintext, pk, l) == true; +ensures pkePredHelper(t2, plaintext, pk, l) == true; +{ + switch (l) { + case Public: + case Readers(list_readers): + switch (list_readers) { + case nil: + case cons(x, xs): + if (ppred(t1, plaintext, pk, x)) { + ppredMonotonic(t1, t2, plaintext, pk, x); + } + // if (pkePredHelper2(t1, plaintext, pk, xs)) { + // pkePredHelper2Monotonic(t1, t2, plaintext, pk, xs); + // } + }; + } +} + +lemma void pkeMonotonic(Trace t1, Trace t2, Term ptxt, Term pk, PkePred pkePred) + requires isSuffix(t1, t2) == true && pkePred(t1, ptxt, pk) == true &*& PkeCtxt(pkePred); + ensures pkePred(t2, ptxt, pk) == true &*& PkeCtxt(pkePred); +{ + open PkeCtxt(pkePred); + pkePredHelperMonotonic(t1, t2, ptxt, pk, getSkLabel(pk)); + close PkeCtxt(pkePred); +} +@*/ diff --git a/VeriFastPrototype/nsl/nsl_pke_pred.gh b/VeriFastPrototype/nsl/nsl_pke_pred.gh new file mode 100644 index 0000000..1286da7 --- /dev/null +++ b/VeriFastPrototype/nsl/nsl_pke_pred.gh @@ -0,0 +1,135 @@ +#ifndef NSL_PKE_PRED +#define NSL_PKE_PRED + +#include "labeling.gh" +#include "nsl_event_types.gh" +#include "protocol_specific_pke_definition.gh" + + +// This file (and corresponding `.c` file) implements `protocol_specific_pke_definition.gh`, +// i.e., provides a body for the `PkeCtxt` predicate, proves monotonicity of `pkePred` (as +// mandated by `protocol_specific_pke_lemma.gh`) and provides a few additional helper lemmas. + +fixpoint bool isMsg1(Term plaintext, Term pk) { + switch (plaintext) { + case integer(value): return false; + case stringTerm(str): return false; + case encrypt(enc, pt): return false; + case hash(term): return false; + case publicKey(str_k): return false; + case nonce(bytes, l): return false; + case tuple2(term1, term2): return false; + case tuple3(term1, term2, term3): return getInteger(term1) == 1; + case tuple4(term1, term2, term3, term4): return false; + } +} + +fixpoint bool isMsg2(Term plaintext, Term pk) { + switch (plaintext) { + case integer(value): return false; + case stringTerm(str): return false; + case encrypt(enc, pt): return false; + case hash(term): return false; + case publicKey(str_k): return false; + case nonce(bytes, l): return false; + case tuple2(term1, term2): return false; + case tuple3(term1, term2, term3): return false; + case tuple4(term1, term2, term3, term4): return getInteger(term1) == 2; + } +} + +fixpoint bool isMsg3(Term plaintext, Term pk) { + switch (plaintext) { + case integer(value): return false; + case stringTerm(str): return false; + case encrypt(enc, pt): return false; + case hash(term): return false; + case publicKey(str_k): return false; + case nonce(bytes, l): return false; + case tuple2(term1, term2): return getInteger(term1) == 3; + case tuple3(term1, term2, term3): return false; + case tuple4(term1, term2, term3, term4): return false; + } +} + +fixpoint bool eventOccursFinishIHelper(EventParams params, int b_given, Term nb_given, int pr) { + switch (params) { + case InitiateParams(a, b, na): return false; + case RespondParams(a, b, na, nb): return false; + case FinishIParams(a, b, na, nb): return a == pr && b == b_given && nb == nb_given; + case FinishRParams(a, b, na, nb): return false; + } +} + +fixpoint bool eventOccursFinishI(trace tr, int b, Term nb) { + switch (tr) { + case root(root_terms): return false; + case makeEvent(t0, pr, e): + return switch(e) { + case newEvent(type, params): + return (type == FINISHI && eventOccursFinishIHelper(params, b, nb, pr)) || eventOccursFinishI(t0, b, nb); + }; + case makeCorrupt(t0, id): return eventOccursFinishI(t0, b, nb); + case makeMessage(t0, to, from, term): return eventOccursFinishI(t0, b, nb); + case makeDropMessage(t0, to, from, term): return eventOccursFinishI(t0, b, nb); + case makeNonce(t0, term): return eventOccursFinishI(t0, b, nb); + case makePublic(t0, term): return eventOccursFinishI(t0, b, nb); + } +} + +fixpoint bool ppred(trace tr, Term plaintext, Term pk, int skOwner) { + switch(plaintext) { + case integer(value): return true; + case stringTerm(str): return true; + case encrypt(enc, pt): return true; + case hash(term): return true; + case publicKey(str_k): return true; + case nonce(bytes, l): return true; + case tuple2(term1, nb): + return isMsg3(plaintext, pk) ? + eventOccursFinishI(tr, skOwner, nb) : true; + case tuple3(term1, na, idA): + return isMsg1(plaintext, pk) ? + nonceOccurs(tr, na, Readers(cons(getInteger(idA), cons(skOwner, nil)))) && + eventOccurs(tr, getInteger(idA), newEvent(INITIATE, InitiateParams(getInteger(idA), skOwner, na))) : true; + case tuple4(term1, na, nb, idB): + return isMsg2(plaintext, pk) ? + nonceOccurs(tr, nb, Readers(cons(skOwner, cons(getInteger(idB), nil)))) && + eventOccurs(tr, getInteger(idB), newEvent(RESPOND, RespondParams(skOwner, getInteger(idB), na, nb))) : true; + } +} + +// `l` is the secret key's label which specifies a single reader: +fixpoint bool pkePredHelper(trace tr, Term plaintext, Term pk, Label l) { + switch(l) { + case Public: + return false; + case Readers(list_readers): + return switch(list_readers) { + case nil: return false; + case cons(x, xs): return ppred(tr, plaintext, pk, x) && xs == nil; + }; + } +} + +fixpoint bool pkePred(Trace tr, Term plaintext, Term pk) { + return pkePredHelper(tr, plaintext, pk, getSkLabel(pk)); +} + +lemma void getPkeCtxt(); + requires true; + ensures PkeCtxt(pkePred); + +lemma void leakPkeCtxt(); + requires PkeCtxt(pkePred); + ensures true; + +lemma void learnPkeCtxt(PkePred p); + requires PkeCtxt(p); + ensures PkeCtxt(p) &*& p == pkePred; + +lemma void eventOccursImpliesEventOccursFinishI(Trace snap, int idA, int idB, Term naT, Term nbT); + requires eventOccurs(snap, idA, newEvent(FINISHI, FinishIParams(idA, idB, naT, nbT))) == true; + ensures eventOccursFinishI(snap, idB, nbT) == true; + +#endif diff --git a/VeriFastPrototype/nsl/pattern.gh b/VeriFastPrototype/nsl/pattern.gh new file mode 100644 index 0000000..6e71be2 --- /dev/null +++ b/VeriFastPrototype/nsl/pattern.gh @@ -0,0 +1,20 @@ +#ifndef PATTERN +#define PATTERN + +#include "bytes.gh" +#include "term.gh" + + +lemma Term PatternPropertyMsg1(Term naT, Term idAT, Term skT, Term t); + requires gamma(t) == gamma(encrypt(publicKey(skT), tuple3(integer(1), naT, idAT))); + ensures t == encrypt(publicKey(skT), tuple3(integer(1), result, idAT)); + +lemma Term PatternPropertyMsg2(Term naT, Term nbT, Term idBT, Term skT, Term t); + requires gamma(t) == gamma(encrypt(publicKey(skT), tuple4(integer(2), naT, nbT, idBT))); + ensures t == encrypt(publicKey(skT), tuple4(integer(2), naT, result, idBT)); + +lemma Term PatternPropertyMsg3(Term nbT, Term skT, Term t); + requires gamma(t) == gamma(encrypt(publicKey(skT), tuple2(integer(3), nbT))); + ensures t == encrypt(publicKey(skT), tuple2(integer(3), nbT)); + +#endif diff --git a/VeriFastPrototype/nsl/protocol_specific_definitions.gh b/VeriFastPrototype/nsl/protocol_specific_definitions.gh new file mode 100644 index 0000000..8525bed --- /dev/null +++ b/VeriFastPrototype/nsl/protocol_specific_definitions.gh @@ -0,0 +1,76 @@ +#ifndef PROTOCOL_SPECIFIC_DEFINITIONS +#define PROTOCOL_SPECIFIC_DEFINITIONS + +#include "labeling.gh" +#include "nsl_event_types.gh" +#include "nsl_pke_pred.gh" +#include "trace_entry.gh" +#include "trace_entry_helpers.gh" + + +fixpoint Term uniqueHelper(EventParams p) { + switch(p) { + case InitiateParams(a, b, na): return na; + case RespondParams(a, b, na, nb): return nb; + case FinishIParams(a, b, na, nb): return na; + case FinishRParams(a, b, na, nb): return nb; + } +} + +fixpoint bool event_pure_pred_helper(EventParams params, int type, int principal, trace tr) { + switch (params) { + case InitiateParams(a, b, na): + return principal == a; + case RespondParams(a, b, na, nb): + return principal == b; + case FinishIParams(a, b, na, nb): + return principal == a && + isLabeled(na, tr, Readers(cons(principal, cons(b, nil))), pkePred) && + (isLabeled(nb, tr, Readers(cons(principal, cons(b, nil))), pkePred) || + isPublishable(tr, nb, pkePred)) && + (containsCorruptId(getCorruptIds(tr), cons(principal, cons(b, nil))) || + eventOccurs(tr, b, newEvent(RESPOND, RespondParams(principal, b, na, nb)))); + case FinishRParams(a, b, na, nb): + return principal == b && + eventOccurs(tr, principal, newEvent(RESPOND, RespondParams(a, principal, na, nb))) && + (containsCorruptId(getCorruptIds(tr), cons(a, cons(principal, nil))) || + eventOccurs(tr, a, newEvent(FINISHI, FinishIParams(a, principal, na, nb)))); + } +} + +// bool isUnique(int type) UNIQUE +#define UNIQUE {\ + return type == INITIATE || type == RESPOND || type == FINISHI || type == FINISHR;\ +} + +// bool eventConsistency(EventParams t, int type) CONSISTENCY +#define CONSISTENCY {\ + switch (t) {\ + case InitiateParams(a, b, na):\ + return type == INITIATE;\ + case RespondParams(a, b, na, nb):\ + return type == RESPOND;\ + case FinishIParams(a, b, na, nb):\ + return type == FINISHI;\ + case FinishRParams(a, b, na, nb):\ + return type == FINISHR;\ + }\ +} + +// Term eventUniquenessWitness(EventADT e) UNIQUE_WITNESS +#define UNIQUE_WITNESS {\ + switch (e) {\ + case newEvent(type, params):\ + return uniqueHelper(params);\ + }\ +} + +//bool event_pure_pred(EventADT e, int principal, trace tr) EVENT_PRED +#define EVENT_PRED {\ + switch (e) {\ + case newEvent(type, params):\ + return event_pure_pred_helper(params, type, principal, tr);\ + }\ +} + +#endif diff --git a/VeriFastPrototype/nsl/protocol_specific_event_lemma.c b/VeriFastPrototype/nsl/protocol_specific_event_lemma.c new file mode 100644 index 0000000..bcf0464 --- /dev/null +++ b/VeriFastPrototype/nsl/protocol_specific_event_lemma.c @@ -0,0 +1,57 @@ +//@ #include "protocol_specific_event_lemma.gh" +//@ #include "nsl_pke_pred.gh" +//@ #include "subset_helpers.gh" +//@ #include "term.gh" + +// this file declares the event lemmas that must be shown by a protocol after instantiating +// this library + + +/*@ +lemma void eventPurePredHelperMonotonic(EventParams params, int type, int principal, trace tr, trace t2, PkePred pkePred) + requires isSuffix(tr, t2) == true &*& event_pure_pred_helper(params, type, principal, tr) == true &*& PkeCtxt(pkePred); + ensures event_pure_pred_helper(params, type, principal, t2) == true &*& PkeCtxt(pkePred); +{ + learnPkeCtxt(pkePred); + switch (params) { + case InitiateParams(a, b, na): + case RespondParams(a, b, na, nb): + case FinishIParams(a, b, na, nb): + isLabeledMonotonic(na, tr, t2, Readers(cons(principal, cons(b, nil))), pkePred); + if (isLabeled(nb, tr, Readers(cons(principal, cons(b, nil))), pkePred)) { + isLabeledMonotonic(nb, tr, t2, Readers(cons(principal, cons(b, nil))), pkePred); + } + if (isPublishable(tr, nb, pkePred)) { + isPublishableMonotonic(tr, t2, nb, pkePred); + } + if (containsCorruptId(getCorruptIds(tr), cons(principal, cons(b, nil)))) { + getCorruptIdsMonotonic(tr, t2); + subset_intersection_helper(getCorruptIds(tr), getCorruptIds(t2), cons(principal, cons(b, nil))); + } + if (eventOccurs(tr, b, newEvent(RESPOND, RespondParams(principal, b, na, nb)))) { + eventOccursMonotonic(tr, t2, b, newEvent(RESPOND, RespondParams(principal, b, na, nb))); + } + case FinishRParams(a, b, na, nb): + if (eventOccurs(tr, principal, newEvent(RESPOND, RespondParams(a, principal, na, nb)))) { + eventOccursMonotonic(tr, t2, principal, newEvent(RESPOND, RespondParams(a, principal, na, nb))); + } + if (containsCorruptId(getCorruptIds(tr),cons(a, cons(principal, nil)))) { + getCorruptIdsMonotonic(tr, t2); + subset_intersection_helper(getCorruptIds(tr), getCorruptIds(t2), cons(a, cons(principal, nil))); + } + if (eventOccurs(tr, a, newEvent(FINISHI, FinishIParams(a, principal, na, nb)))) { + eventOccursMonotonic(tr, t2, a, newEvent(FINISHI, FinishIParams(a, principal, na, nb))); + } + } +} + +lemma void eventPurePredMonotonic(Trace t1, Trace t2, int principal, ParameterizedEvent e, PkePred pkePred) + requires isSuffix(t1, t2) == true &*& event_pure_pred(e, principal, t1) == true &*& PkeCtxt(pkePred); + ensures event_pure_pred(e, principal, t2) == true &*& PkeCtxt(pkePred); +{ + switch (e) { + case newEvent(type, params): + eventPurePredHelperMonotonic(params, type, principal, t1, t2, pkePred); + } +} +@*/ diff --git a/VeriFastPrototype/nsl/protocol_specific_event_params.gh b/VeriFastPrototype/nsl/protocol_specific_event_params.gh new file mode 100644 index 0000000..8afe21f --- /dev/null +++ b/VeriFastPrototype/nsl/protocol_specific_event_params.gh @@ -0,0 +1,13 @@ +#ifndef PROTOCOL_SPECIFIC_EVENT_PARAMS +#define PROTOCOL_SPECIFIC_EVENT_PARAMS + +#include "term.gh" + +// inductive EventParams = EVENT_PARAMS +#define EVENT_PARAMS \ + | InitiateParams(int a, int b, Term na)\ + | RespondParams(int a, int b, Term na, Term nb)\ + | FinishIParams(int a, int b, Term na, Term nb)\ + | FinishRParams(int a, int b, Term na, Term nb); + +#endif diff --git a/VeriFastPrototype/nsl/responder.c b/VeriFastPrototype/nsl/responder.c new file mode 100644 index 0000000..6779a79 --- /dev/null +++ b/VeriFastPrototype/nsl/responder.c @@ -0,0 +1,572 @@ +#include "responder.h" +#include +#include +//@ #include "bytes.gh" +//@ #include "labeling.gh" +#include "messages.h" +//@ #include "nsl_pke_pred.gh" +//@ #include "pattern.gh" +//@ #include "security_properties.gh" +//@ #include "subset_helpers.gh" +//@ #include "term.gh" + + +/*@ +predicate NaNonce(Trace snap, int idA, int idB, char *na, Term naT) = + chars(na, NonceLength, ?abs_na) &*& malloc_block(na, NonceLength) &*& + gamma(naT) == abs_na &*& + (containsCorruptId(getCorruptIds(snap), cons(idA, cons(idB, nil))) || isLabeled(naT, snap, Readers(cons(idA, cons(idB, nil))), pkePred)); + +predicate NbNonce(Trace snap, int idA, int idB, char *nb, Term nbT) = + chars(nb, NonceLength, ?abs_nb) &*& malloc_block(nb, NonceLength) &*& + gamma(nbT) == abs_nb &*& + isLabeled(nbT, snap, Readers(cons(idA, cons(idB, nil))), pkePred) == true; + +predicate Mem(struct B* b, int version) = + malloc_block_B(b) &*& + b->labeledLib |-> ?labeledLib &*& LabeledLibMem(labeledLib, ?snap, ?idB, pkePred) &*& + b->version |-> version &*& 0 <= version &*& + b->idB |-> idB &*& + b->pkB |-> ?pkB &*& + b->pkB_len |-> ?pkB_len &*& chars(pkB, pkB_len, ?abs_pkB) &*& malloc_block(pkB, pkB_len) &*& + b->skB |-> ?skB &*& + b->skB_len |-> ?skB_len &*& + chars(skB, skB_len, ?abs_skB) &*& malloc_block(skB, skB_len) &*& + b->idA |-> ?idA &*& + b->pkA |-> ?pkA &*& + b->pkA_len |-> ?pkA_len &*& + b->skBT |-> ?skBT &*& + b->skAT |-> ?skAT &*& + abs_skB == gamma(skBT) &*& + isSecretKey(snap, idB, skBT, 1, pkePred) == true &*& + (1 <= version ? + chars(pkA, pkA_len, ?abs_pkA) &*& malloc_block(pkA, pkA_len) &*& + abs_pkA == gamma(publicKey(skAT)) &*& + isPublicKey(snap, idA, publicKey(skAT), skAT, 1, pkePred) == true : true) &*& + (2 <= version ? + NaNonce(snap, idA, idB, ?na, ?naT) &*& + NbNonce(snap, idA, idB, ?nb, ?nbT) &*& + eventOccurs(snap, idB, newEvent(FINISHR, FinishRParams(idA, idB, naT, nbT))) == true &*& + (3 <= version ? + Secrecy(naT, snap, cons(idA, cons(idB, nil)), pkePred) == true &*& + Secrecy(nbT, snap, cons(idA, cons(idB, nil)), pkePred) == true &*& + InjectiveAgreement(snap, idB, idA, FINISHR, FinishRParams(idA, idB, naT, nbT), FINISHI, FinishIParams(idA, idB, naT, nbT), cons(idA, cons(idB, nil))) : true) + : true); +@*/ + +struct B *initB(struct IoLib *io_lib, struct TraceManager *tm, int initiator, int responder) +//@ requires IoLibMem(io_lib) &*& TraceManagerMem(tm, responder, ?snap0, pkePred); +//@ ensures result != 0 ? Mem(result, 0) : emp; +{ + struct B* b = malloc(sizeof(struct B)); + if(b == 0) { + abort(); + } + //@ getPkeCtxt(); + b->labeledLib = createLabeledLib(io_lib, tm, responder); + if(b->labeledLib == 0) { + abort(); + } + b->version = 0; + b->idB = responder; + b->idA = initiator; + + // generate keypair + //@ close exists(Readers(cons(responder, nil))); + struct keypair *keys = labeledGeneratePkeKey(b->labeledLib); + if (keys == 0) { + abort(); + } + char *skB = keys->sk; + b->skB = skB; + b->skB_len = keys->sk_len; + b->pkB = keys->pk; + b->pkB_len = keys->pk_len; + free(keys); + //@ assert chars(skB, _, ?abs_sk); + //@ b->skBT = nonce(abs_sk, Readers(cons(responder, nil))); + + //@ close Mem(b, 0); + return b; +} + +void responderNonInjectiveAgreement(struct LabeledLib *lib) +/*@ requires LabeledLibMem(lib, ?snap, ?owner, pkePred) &*& + NaNonce(snap, ?idA, ?idB, ?na, ?naT) &*& + NbNonce(snap, idA, idB, ?nb, ?nbT) &*& + eventOccurs(snap, idB, newEvent(FINISHR, FinishRParams(idA, idB, naT, nbT))) == true; @*/ +/*@ ensures LabeledLibMem(lib, snap, owner, pkePred) &*& + NaNonce(snap, idA, idB, na, naT) &*& + NbNonce(snap, idA, idB, nb, nbT) &*& + NonInjectiveAgreement(snap, idB, idA, FINISHR, FinishRParams(idA, idB, naT, nbT), FINISHI, FinishIParams(idA, idB, naT, nbT), cons(idA, cons(idB, nil))) == true; @*/ +{ + //@ ParameterizedEvent ev = newEvent(FINISHR, FinishRParams(idA, idB, naT, nbT)); + //@ close exists(idB); + //@ close exists(ev); + //@ getPkeCtxt(); + labeledEventOccursImpliesEventInv(lib); + //@ assert exists(?finishRWitness); + /*@ + if (!eventOccurs(finishRWitness, idA, newEvent(FINISHI, FinishIParams(idA, idB, naT, nbT)))) { + getCorruptIdsMonotonic(finishRWitness, snap); + subset_intersection_helper(getCorruptIds(finishRWitness), getCorruptIds(snap), cons(idA, cons(idB, nil))); + } + @*/ + //@ leakPkeCtxt(); +} + +void responderInjectiveAgreement(struct LabeledLib *lib) +/*@ requires LabeledLibMem(lib, ?snap, ?owner, pkePred) &*& + NaNonce(snap, ?idA, ?idB, ?na, ?naT) &*& + NbNonce(snap, idA, idB, ?nb, ?nbT) &*& + eventOccurs(snap, idB, newEvent(FINISHR, FinishRParams(idA, idB, naT, nbT))) == true; @*/ +/*@ ensures LabeledLibMem(lib, snap, owner, pkePred) &*& + NaNonce(snap, idA, idB, na, naT) &*& + NbNonce(snap, idA, idB, nb, nbT) &*& + InjectiveAgreement(snap, idB, idA, FINISHR, FinishRParams(idA, idB, naT, nbT), FINISHI, FinishIParams(idA, idB, naT, nbT), cons(idA, cons(idB, nil))); @*/ +{ + responderNonInjectiveAgreement(lib); + //@ close exists(idB); + //@ list bothIds = cons(idA, cons(idB, nil)); + //@ ParameterizedEvent finishREv = newEvent(FINISHR, FinishRParams(idA, idB, naT, nbT)); + //@ close exists(finishREv); + //@ getPkeCtxt(); + labeledUniqueEventIsUnique(lib); + //@ assert EventIsUnique(snap, idB, finishREv); + //@ close InjectiveAgreement(snap, idB, idA, FINISHR, FinishRParams(idA, idB, naT, nbT), FINISHI, FinishIParams(idA, idB, naT, nbT), bothIds); + //@ Trace finishRWitness = getEventTrace(snap, idB, finishREv); + /*@ + if (containsCorruptId(getCorruptIds(finishRWitness), bothIds)) { + leak EventIsUnique(snap, idB, finishREv); + } + @*/ + //@ leakPkeCtxt(); +} + +void responderProveSecurityProperties(struct B* b) +//@ requires Mem(b, 2); +//@ ensures Mem(b, 3); +{ + //@ open Mem(b, 2); + struct LabeledLib *lib = b->labeledLib; + + responderInjectiveAgreement(lib); + + //@ open NaNonce(?snap, ?idA, ?idB, ?na, ?naT); + //@ list bothIds = cons(idA, cons(idB, nil)); + //@ Label both_label = Readers(bothIds); + //@ close exists(naT); + //@ close exists(bothIds); + //@ getPkeCtxt(); + labeledSecrecyLemma(lib); + //@ close NaNonce(snap, idA, idB, na, naT); + + //@ open NbNonce(snap, idA, idB, ?nb, ?nbT); + //@ close exists(nbT); + //@ close exists(bothIds); + labeledSecrecyLemma(lib); + //@ close NbNonce(snap, idA, idB, nb, nbT); + + b->version = 3; + //@ close Mem(b, 3); + //@ leakPkeCtxt(); +} + +/*@ +lemma triple getEventOccursFinishIWitness(Trace snap, int idB, Term nbT) + requires eventOccursFinishI(snap, idB, nbT) == true; + ensures eventOccurs(snap, triple_fst(result), newEvent(FINISHI, FinishIParams(triple_snd(result), idB, triple_thrd(result), nbT))) == true; +{ + switch(snap) { + case root(root_terms): + case makeEvent(t0, p, e): + switch(e) { + case newEvent(type, params): + bool found = false; + triple res; + switch (params) { + case InitiateParams(a, b, na): + case RespondParams(a, b, na, nb): + case FinishIParams(a, b, na, nb): + if (type == FINISHI && b == idB && nb == nbT) { + found = true; + res = triple(p, a, na); + assert eventOccurs(snap, p, newEvent(FINISHI, params)) == true; + assert eventOccurs(snap, p, newEvent(FINISHI, FinishIParams(a, idB, na, nbT))) == true; + } + case FinishRParams(a, b, na, nb): + } + if (!found) { + res = getEventOccursFinishIWitness(t0, idB, nbT); + } + return res; + } + case makeCorrupt(t0, id): + return getEventOccursFinishIWitness(t0, idB, nbT); + case makeMessage(t0, to, from, term): + return getEventOccursFinishIWitness(t0, idB, nbT); + case makeDropMessage(t0, to, from, term): + return getEventOccursFinishIWitness(t0, idB, nbT); + case makeNonce(t0, term): + return getEventOccursFinishIWitness(t0, idB, nbT); + case makePublic(t0, term): + return getEventOccursFinishIWitness(t0, idB, nbT); + } +} +@*/ + +bool runB(struct B* b) +//@ requires Mem(b, 1); +//@ ensures result ? Mem(b, 3) : emp; +{ + unsigned int msg1Data_len = 0; + unsigned int ciphertext1_len = 0; + unsigned int msg2Data_len = 0; + unsigned int ciphertext2_len = 0; + unsigned int msg3Data_len = 0; + unsigned int ciphertext3_len = 0; + + //@ open Mem(b, 1); + struct LabeledLib *lib = b->labeledLib; + //@ assert LabeledLibMem(lib, ?snap0, ?idB, pkePred) &*& b->skBT |-> ?skBT &*& b->skAT |-> ?skAT; + //@ int idA = b->idA; + + char *ciphertext1 = labeledReceive(lib, b->idA, b->idB, &ciphertext1_len); + //@ assert LabeledLibMem(lib, ?snap1, idB, pkePred); + if (ciphertext1 == 0){ + abort(); + } + + // apply monotonicity snap0 -> snap1: + //@ getPkeCtxt(); + //@ isSecretKeyMonotonic(snap0, snap1, idB, skBT, 1, pkePred); + //@ isPublicKeyMonotonic(snap0, snap1, idA, skAT, 1, pkePred); + + /*@ assert exists(?ciphertext1T) &*& + chars(ciphertext1, ?ciphertext1Size, ?abs_ciphertext1) &*& + gamma(ciphertext1T) == abs_ciphertext1; @*/ + //@ leak exists(ciphertext1T); + //@ close exists(skBT); + //@ close exists(ciphertext1T); + + char *skB = b->skB; + //@ assert chars(skB, _, ?abs_sk); + //@ close exists(idB); + char *msg1Data = labeledDec(lib, ciphertext1, ciphertext1_len, skB, b->skB_len, &msg1Data_len); + free(ciphertext1); + if (msg1Data == 0){ + abort(); + } + //@ assert chars(msg1Data, _, ?abs_msg1); + struct Msg1 *msg1 = unmarshalMsg1(msg1Data, msg1Data_len); + free(msg1Data); + if (msg1 == 0){ + abort(); + } + + // check idA: + if (msg1->idA != b->idA){ + abort(); + } + + char *na = msg1->Na; + //@ assert chars(na, NonceLength, ?abs_na); + //@ Term naT = PatternPropertyMsg1(oneTerm(abs_na), integer(idA), skBT, ciphertext1T); + free(msg1); + + //@ Term msg1T = tuple3(integer(1), naT, integer(idA)); + + /*@ assert [_]is_forall_t(?quantifiedExp1) &*& + quantifiedExp1((msgIsDecrypted)(snap1, ciphertext1T, skBT, idB, pkePred)) == true; @*/ + //@ forall_t_elim(quantifiedExp1, (msgIsDecrypted)(snap1, ciphertext1T, skBT, idB, pkePred), msg1T); + //@ assert wasDecrypted(snap1, msg1T, skBT, idB, pkePred) == true; + // we no longer need the quantifier and thus leak the corresponding resource: + //@ leak [_]is_forall_t(quantifiedExp1); + + //@ assert chars(skB, _, ?abs_skB); + // the following assertion is needed: + //@ assert decryptB(abs_skB, encryptB(publicKeyB(abs_skB), abs_msg1)) == abs_msg1; + + // the following 2 assertions seems to be needed: + //@ assert abs_msg1 == tuple3B(integerB(1), abs_na, integerB(idA)); + //@ assert getSecondB(abs_msg1) == abs_na; + + //@ Label msg1Label = isPublishable(snap1, msg1T, pkePred) ? Public : Readers(cons(idB, nil)); + //@ isMsgTupleThreeResolve(snap1, integer(1), naT, integer(idA), msg1Label, pkePred); + //@ close exists(naT); + //@ close exists(msg1Label != Public); + labeledNonceOccursImpliesRandInvConditional(lib); + //@ Label both_label = Readers(cons(idA, cons(idB, nil))); + /*@ + if (msg1Label == Public) { + isMsgTransitive(snap1, naT, Public, both_label, pkePred); + } + @*/ + + // facts after receiving msg1: + //@ assert isMsg(snap1, naT, both_label, pkePred) == true; + //@ assert isPublishable(snap1, naT, pkePred) || nonceOccurs(snap1, naT, both_label); + + // create nonce nb + //@ list eventTypes = cons(RESPOND, cons(FINISHR, nil)); + //@ close exists(eventTypes); + //@ close exists(both_label); + char *nb = labeledCreateNonce(lib); + //@ assert LabeledLibMem(lib, ?snap2, idB, pkePred); + if(nb == 0) { + abort(); + } + //@ assert chars(nb, NonceLength, ?abs_nb); + //@ Term nbT = nonce(abs_nb, both_label); + + // apply monotonicity snap1 -> snap2: + //@ isSecretKeyMonotonic(snap1, snap2, idB, skBT, 1, pkePred); + //@ isPublicKeyMonotonic(snap1, snap2, idA, skAT, 1, pkePred); + /*@ + if (msg1Label == Public) { + isPublishableMonotonic(snap1, snap2, naT, pkePred); + } else { + nonceOccursMonotonic(snap1, snap2, naT, both_label); + } + @*/ + + //@ ParameterizedEvent respond = newEvent(RESPOND, RespondParams(idA, idB, naT, nbT)); + //@ close exists(respond); + //@ open eventUniquePointer(eventTypes, nbT); + //@ close event_pred(idB, respond, snap2); + labeledTriggerEvent(lib); + //@ assert LabeledLibMem(lib, ?snap3, idB, pkePred); + + // apply monotonicity snap2 -> snap3: + //@ isSecretKeyMonotonic(snap2, snap3, idB, skBT, 1, pkePred); + //@ isPublicKeyMonotonic(snap2, snap3, idA, skAT, 1, pkePred); + //@ nonceOccursMonotonic(snap2, snap3, nbT, both_label); + /*@ + if (msg1Label == Public) { + isPublishableMonotonic(snap2, snap3, naT, pkePred); + isMsgTransitive(snap3, naT, Public, Readers(cons(idA, nil)), pkePred); + } else { + nonceOccursMonotonic(snap2, snap3, naT, both_label); + } + @*/ + + struct Msg2 *msg2 = malloc(sizeof(struct Msg2)); + if (msg2 == 0) { + abort(); + } + msg2->Na = na; + msg2->Nb = nb; + msg2->idB = b->idB; + char *msg2Data = marshalMsg2(msg2, &msg2Data_len); + free(msg2); + if (msg2Data == 0) { + abort(); + } + //@ assert chars(msg2Data, ?msg2Size, ?abs_msg2); + + //@ Term msg2T = tuple4(integer(2), naT, nbT, integer(idB)); + //@ Term pkAT = publicKey(b->skAT); + //@ close exists(pkAT); + //@ close exists(msg2T); + //@ char *pkA = b->pkA; + //@ unsigned int pkA_len = b->pkA_len; + //@ open chars(pkA, pkA_len, ?abs_pkA); + //@ close chars(pkA, pkA_len, abs_pkA); + //@ Label justALabel = Readers(cons(idA, nil)); + //@ isMsgTupleFourCreate(snap3, integer(2), naT, nbT, integer(idB), justALabel, pkePred); + //@ assert canEncrypt(snap3, msg2T, pkAT, pkePred) == true; + char *ciphertext2 = labeledEnc(lib, msg2Data, msg2Data_len, b->pkA, b->pkA_len, &ciphertext2_len); + //@ leak exists(msg2T); + //@ leak exists(pkAT); + free(msg2Data); + if (ciphertext2 == 0) { + abort(); + } + + //@ Term ciphertext2T = encrypt(pkAT, msg2T); + //@ close exists(ciphertext2T); + bool success = labeledSend(lib, b->idB, b->idA, ciphertext2, ciphertext2_len); + //@ assert LabeledLibMem(lib, ?snap4, idB, pkePred); + //@ leak exists(ciphertext2T); + free(ciphertext2); + if (!success){ + abort(); + } + + // apply monotonicity snap3 -> snap4: + //@ isSecretKeyMonotonic(snap3, snap4, idB, skBT, 1, pkePred); + //@ isPublicKeyMonotonic(snap3, snap4, idA, skAT, 1, pkePred); + //@ nonceOccursMonotonic(snap3, snap4, nbT, both_label); + //@ eventOccursMonotonic(snap3, snap4, idB, respond); + /*@ + if (msg1Label == Public) { + isPublishableMonotonic(snap3, snap4, naT, pkePred); + } else { + nonceOccursMonotonic(snap3, snap4, naT, both_label); + } + @*/ + + char *ciphertext3 = labeledReceive(lib, b->idA, b->idB, &ciphertext3_len); + //@ assert LabeledLibMem(lib, ?snap5, idB, pkePred); + if (ciphertext3 == 0){ + abort(); + } + + // apply monotonicity snap4 -> snap5: + //@ isSecretKeyMonotonic(snap4, snap5, idB, skBT, 1, pkePred); + //@ isPublicKeyMonotonic(snap4, snap5, idA, skAT, 1, pkePred); + //@ nonceOccursMonotonic(snap4, snap5, nbT, both_label); + //@ eventOccursMonotonic(snap4, snap5, idB, respond); + /*@ + if (msg1Label == Public) { + isPublishableMonotonic(snap4, snap5, naT, pkePred); + } else { + nonceOccursMonotonic(snap4, snap5, naT, both_label); + } + @*/ + + /*@ assert exists(?ciphertext3T) &*& + chars(ciphertext3, ?ciphertext3Size, ?abs_ciphertext3) &*& + gamma(ciphertext3T) == abs_ciphertext3; @*/ + //@ close exists(skBT); + //@ open chars(ciphertext3, ciphertext3Size, abs_ciphertext3); + //@ close chars(ciphertext3, ciphertext3Size, abs_ciphertext3); + //@ close exists(idB); + char *msg3Data = labeledDec(lib, ciphertext3, ciphertext3_len, skB, b->skB_len, &msg3Data_len); + free(ciphertext3); + if (msg3Data == 0){ + abort(); + } + //@ assert chars(msg3Data, _, ?abs_msg3); + struct Msg3 *msg3 = unmarshalMsg3(msg3Data, msg3Data_len); + free(msg3Data); + if (msg3 == 0){ + abort(); + } + + // check nb: + if(memcmp(msg3->Nb, nb, NonceLength) != 0){ + abort(); + } + + //@ PatternPropertyMsg3(nbT, skBT, ciphertext3T); + free(msg3->Nb); + free(msg3); + + //@ Term msg3T = tuple2(integer(3), nbT); + + /*@ assert [_]is_forall_t(?quantifiedExp3) &*& + quantifiedExp3((msgIsDecrypted)(snap5, ciphertext3T, skBT, idB, pkePred)) == true; @*/ + //@ forall_t_elim(quantifiedExp3, (msgIsDecrypted)(snap5, ciphertext3T, skBT, idB, pkePred), msg3T); + //@ assert wasDecrypted(snap5, msg3T, skBT, idB, pkePred) == true; + // we no longer need the quantifier and thus leak the corresponding resource: + //@ leak [_]is_forall_t(quantifiedExp3); + + //@ Label msg3Label; + //@ bool nbTIsPublishable = false; + //@ int principalW; + //@ int idAW; + //@ Term naTW; + /*@ + if (isPublishable(snap5, msg3T, pkePred)) { + msg3Label = Public; + isMsgTupleTwoResolve(snap5, integer(3), nbT, Public, pkePred); + } else { + msg3Label = Readers(cons(idB, nil)); + // assert eventOccursFinishI(snap5, idB, nbT) == true; + + // FinishI event must have occurred based on pke_pred, which in turn implies that the + // respond event has occurred. + // By uniqueness of the respond event, we know that the respond events + // from the current program state and pke_pred must be the same one: + + triple witnesses = getEventOccursFinishIWitness(snap5, idB, nbT); + principalW = triple_fst(witnesses); + idAW = triple_snd(witnesses); + naTW = triple_thrd(witnesses); + + if (isPublishable(snap5, nbT, pkePred)) { + publishableImpliesCorruption(snap5, nbT, both_label, cons(idA, cons(idB, nil)), pkePred); + } else { + // from the FinishI event, we learn that either idAWitness or b.IdB must have been corrupted or the + // Respond event has occurred. + // However, b.IdB cannot have been corrupted because nbT would otherwise be publishable and we would + // not reach this branch. + // Therefore, we distinguish two cases here: + // (1) idAWitness has not been corrupted. Thus, Respond event has occurred and due to uniqueness of + // the Respond event, we know that idAWitness == b.IdA + // (2) idAWitness has been corrupted. In this case, we can use the fact that we know the labeling of + // nbT (because we have created the nonce) and nbT's labeling given by `pureEventInv`: Because + // the labeling is unique, idAWitness must be equal to b.IdA + + nbTIsPublishable = true; + // we continue below... + } + } + @*/ + + //@ close exists(principalW); + //@ ParameterizedEvent ev = newEvent(FINISHI, FinishIParams(idAW, idB, naTW, nbT)); + //@ close exists(ev); + //@ close exists(nbTIsPublishable); + labeledEventOccursImpliesEventInvConditional(lib); + /*@ + if (nbTIsPublishable) { + // we've now learnt the event invariant for FinishI and can thus deduce that: + // assert principalW == idAW; + if (containsCorruptId(getCorruptIds(snap5), cons(idAW, nil))) { + // this is a contradiction because we know that b.IdA has not been corrupted, otherwise nbT would be publishable: + assert false; + } + // use uniqueness below to learn that naT == naTW; + } + @*/ + //@ close exists(nbTIsPublishable); + //@ close Event1(idB, respond); + //@ close Event2(idB, newEvent(RESPOND, RespondParams(idAW, idB, naTW, nbT))); + labeledUniqueEventsAreUniqueGhostArgsConditional(lib); + + // facts after receiving msg3: + //@ bool corruptionOccurred = containsCorruptId(getCorruptIds(snap5), cons(idA, cons(idB, nil))); + /*@ + if (!corruptionOccurred) { + assert eventOccurs(snap5, idA, newEvent(FINISHI, FinishIParams(idA, idB, naT, nbT))) == true; + } + @*/ + + //@ ParameterizedEvent finishR = newEvent(FINISHR, FinishRParams(idA, idB, naT, nbT)); + //@ close exists(finishR); + //@ open eventUniquePointer(cons(FINISHR, nil), nbT); + //@ close event_pred(idB, finishR, snap5); + labeledTriggerEvent(lib); + //@ open eventUniquePointer(nil, nbT); + //@ assert LabeledLibMem(lib, ?snap6, idB, pkePred); + + // apply monotonicity snap5 -> snap6: + //@ isSecretKeyMonotonic(snap5, snap6, idB, skBT, 1, pkePred); + //@ isPublicKeyMonotonic(snap5, snap6, idA, skAT, 1, pkePred); + //@ nonceOccursMonotonic(snap5, snap6, nbT, both_label); + + /*@ + if (corruptionOccurred) { + getCorruptIdsMonotonic(snap5, snap6); + containsCorruptIdMonotonic2(getCorruptIds(snap5), getCorruptIds(snap6), cons(idA, cons(idB, nil))); + } else { + nonceOccursMonotonic(snap5, snap6, naT, both_label); + } + @*/ + + b->version = 2; + + print_hex("B has successfully finished the protocol run\nB.na", na, NonceLength); + print_hex("B.nb", nb, NonceLength); + + //@ close NaNonce(snap6, idA, idB, na, naT); + //@ close NbNonce(snap6, idA, idB, nb, nbT); + //@ leakPkeCtxt(); + //@ close Mem(b, 2); + + responderProveSecurityProperties(b); + + return true; +} diff --git a/VeriFastPrototype/nsl/responder.h b/VeriFastPrototype/nsl/responder.h new file mode 100644 index 0000000..ee816ad --- /dev/null +++ b/VeriFastPrototype/nsl/responder.h @@ -0,0 +1,33 @@ +#ifndef RESPONDER +#define RESPONDER + +#include "labeled_library.h" + + +struct B { + struct LabeledLib *labeledLib; + int version; + int idB; + char *pkB; + unsigned int pkB_len; + char *skB; + unsigned int skB_len; + int idA; + char *pkA; + unsigned int pkA_len; + //@ Term skBT; + //@ Term skAT; +}; + +/*@ +predicate Mem(struct B* b, int version); +@*/ + +struct B *initB(struct IoLib *io_lib, struct TraceManager *tm, int initiator, int responder); +//@ requires IoLibMem(io_lib) &*& TraceManagerMem(tm, responder, ?snap0, pkePred); +//@ ensures result != 0 ? Mem(result, 0) : emp; + +bool runB(struct B* b); +//@ requires Mem(b, 1); +//@ ensures result ? Mem(b, 3) : emp; +#endif diff --git a/VeriFastPrototype/nsl/runInitiator.sh b/VeriFastPrototype/nsl/runInitiator.sh new file mode 100755 index 0000000..58b2815 --- /dev/null +++ b/VeriFastPrototype/nsl/runInitiator.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# exit when any command fails +set -e + +SCRIPT_DIR=$(dirname "$0") + +$SCRIPT_DIR/nsl \ + --isInitiator \ + --address "127.0.0.1" \ + --port 3000 \ + --privateKey "MIIJKAIBAAKCAgEAvVMEI16XJOvbU1WjpnP/ne2ndS7zuFMgidUxrSVB6loHpt59ZBfhczjnl0ZkSOvbGmJrbAfqIjHs9oFwmZoTp/hgoJF0bXFbJZQixx79gt3SNwt9cqUvA0851lY00dYkfiqRM/rT9e6C0VAGuIWo/VOpf4zDIrYKwC4DbDQ6e+DyuNqgym+5PnAezo5wwqA05k7TRhq5fBIDuH5LepemMwz0uJmwascsU6KNXFVMrc+e8lu1uiBeELiXH2IliOw0GgoYxx26OLM8+o/uBLoHcxiprR3nXangHsr+3I7LyIZtRyOIycok1/XVj6Nch5bzWX53+7xrSC0P4OncNPIm++O7NOqLAE2kZfAzHrNA2oBu7wUDE4BVnvM9QG0KzTIY9QVQbawNzY+SzmpolkEvJ5AfvdtGQond/vgi8+DLZwyP5CH1mJMiT1TSFlPb8C17FuYdz3mHMgBM6Z/6edVcmeul4gujFcc+umodDpZqw24wdx+t3UStOwCArX/JMbSH9Zu17RdnwiPUK8cYa756rqzScJhIReZuDZgG2jqxWcr3IvJcrllDiwyhB/pELn9OFYRsd0QdHNA16GLwRZejmxJXp5OC9UVzViJ+O4NeW+Sx0tb5o0yIbBEUHY1Kl7Z519svRrXCKASk0EfAyWCcrY38TfyvmMNwspNrh3K/pnMCAwEAAQKCAgBVrHCN9OseySCqOHHjDFEbTYVfEQ03V169IN3nBZori+w0hjBmECx0sMaUfUU6fojbCrij3X0FVmRuNKsYx1GnzE0lvEzcjdR6T+vhAdQk2W6cfDWboMaCj+KTbNVgM7C161tkE1jBzNokEDvKWqnbYXWtg6x2U7zPtMLVv1jL4ELWhhEHKsHAUIqQXIMIf+kQY5FWAxf23kwSvAw6ANAz/+PqeZoM5+7WNhQUOYGGkhLSh8/X13fZxz6T9B0aNhFpyzHlQT2ZFPs+Q82pE+n1Gq8F6SdfClWieagVdQUgzDw9WgY3kqNTmyq2Ym2n6hZbZFC1eVFvCv1JgWqmBwK+Z/slaVhROUZ5/AMB0Dhd8v5aYp5d7o+xMClbCB+hHvzGFXTem+wm9pIGAqXZUPE3jWEz2xf6QsQ+NypubuZRUrxzcxOM1k+N7xRLc17le05vusqWT/l+InBfZEhYRaTo2BduvWZNX5O3B3IlMCdPRYPY8ux+rH0MYzSVLmhtrURX526nXJkVfdMEj0iltO2K+bdfAleiFXs1AY+BcE3asINvxBm3QqpjG5Pyx/H+Ahsl4BwrSP+jUlJDQZnsLD9RDsgRxAVg9Qgn5Et4JsnjScAtiQvsRE832ZDqxgQcVT/lBLZ0ghOVcI1bMCKQ0mIxWOBp7CzO5I2eXE6ePRkzoQKCAQEAyNTKK7ZprS8XJpSHxf2VY6b8vn6RVlZtuhfITSO0IqJVgtDUWttHuN4s8IJ8Go7aCDuuKbP0xnha7ssEZMNjcwutiZXPkvOflDvDq/dHIkuCa4w7HIP28a/P4naaS9A4whUjPM4+jtHkk4EWK/8oifsQHNg6NEBJs7wI27vQ2Pq0J4Jn8VXQ7MbLl7J+Lqdp6yFxVq8IYudJXJudnCQmW+lAAy+8LpICcOjzeuXdrmsuZh7J8qO+CaspCBL2G2QSMEbvyDLL0P8D/05fBTyI8lrM5n4SoxNHw8IsdD7X3wiJBMid09ot8/ikPPzfeJVqEOh9NP9lxyomMA/PgTJqUQKCAQEA8VUEB1m04rP3JHRR7dbtjf7/iNS60RB3lYnz9tNqLcng3GAgPwU226kVsSUf+H3129tQpabwm9CZsRnxmLNOWkEZAATWwLL2KrmAn8+FkqzC5lzYeUr2Pj/b3nCgq5vYCukjhOxzhYRaM06dAjOa/OdfnYrg4wpvWeXQCvDU3bWpJMyPIAqElyXwbHfL3bW7Cf2BTw8bX4UNnW5exyU8oeMcw6PdxBilJ5m+eL/msWdr/SopVUgPsqp/7ZBxuiTrZkfsdRhfkvwhEbIC2ehRGSMJ5kjHuUAAbd+bkExf1UPvixdUWCw5zYaZXMfBXLdKTHSMOmAuAGxU3hjkr5CPgwKCAQAv+eVyG9mS7bTyGnl06udNLw8h0sqVfYAo/JV1GBpoS69x2MFiExBHMYw6yHEtRwL/BILOo5bN8uKGOSmLiMGxMhD61TcJO/nbR4uvARuVLcSyPIXCgiP0CLP4vayOf+ePNc19MSfwpmOceTH6wLHGhJuMyHrfEJyKu1jCZVO3Ae0XoyeBl6aZacQpMRLDwmqjKRISSy4NsoLsBKDaNCiVvFr2Z+jklyzOHFhN+6vBhwlGjARiWouDc8gRjbYNRRKzRb7ybHAUNVeXHfnFHnj4rIhWZ7e8DVcPhMtp2bloJnnVErfhbDWeGr/hcedQvyDfeSqwBnMh6QfGY4CGtKyBAoIBAQC5ek6zW4XDau96TBAfzL0dEivRPTYrsg1GmBUx0cDcWjkBBrwh018bKfPrBw5wTFbmV8O+3PQ1vPgyfi8J3l1MzpVpR07KIYFCyvmJWdReK3tL03Xomu1wYGIartM9sXQ0xoQvCA+tQVCV+EiBxanL0APTsEYxGPcFz7O6hOgFUjYiezlRNeQ7ysPiiZvc1WxgPD7ixUiTfE4/ffFH+12DSmr0DgBGU26zZd1XLp4eIM+Fbp7/1XeDKLlTm11c5D0rigG46TejXzYHRJoeYgfaWuyj2bHutbretyn3mEtbPHBhpVeEwNDYHifGgBwjpxdqdXTE9ODGIHyFifpQ2LkDAoIBACFaCJXJpoShj2sBfwqvYMMyQLlHpSqEDu6Q1TlNqhIevyTIAQfpxM84FV0J77hVUU7r9KBg6qqkvKYoUZhBTqzpGtCpt0vBAKHIFfs/Cg17B/9F1938YypkDkpLfY28AekmG2ZuPAUlJX/hXsMMs+iAVvb96B48vSyJn3kiRYF5Sqixk9IBtJc0m7hPVEvCa3q/PXYIL4+klgtbxttd+Ln0uiBGj5KXMpYLxfWg9ZRqAfqGpss4+s+A2FWkY6uozhznCGuqyCjcC0GRL1suqpZqLbZfNam9HnkYjdIe09ZALF5RhWa9anBpfJD2/G34pElZRhvhDHH7quwkRMVlbWM=" \ + --peerPublicKey "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAx+KThX2pDDgGcNzmDxBfldrN+v7sWoW3FDtszT0Bb/pqVh5Wa2FI8S+eO3SJgytj2B3Kj9GjGy9UqJNTBAJCcm/xkEVCvVQiSro0id+hTN7zSkIDYiUs01LxjF9eZ71715jeXLiNWQNBTaiLrRuvvhQeRSQ328UzKj5aGklgUxmI/9VWOngmELYdsPET/FdxkM0481a+G4o5D7wRZLxAk/EbeNB0ifi4VB4sysMFGX06330KtmJq3jH/jKIOLxu1+OwLT8CAPM/t/J3Vgerr9KBcnEmyIt91+1OJOAUhQGRx0VOnU3qR3WPRUaDrL/5XeozSd45wK2dFZlLcPQ6CSizVPe0lPNobwiwJ/GS3m5YT2EUlOvKdqRMK8n9I3stsaC3sVcFQRIwPmWqg5B7y4QO1vQfBMyz8rc1kxfwETo5so3IGPzxKQWkF4Az2wdKyljuRApAK5DgF4q7mLiCeGXofuBFp1KqWV5QLUKBxNKy5qCmbv+FarCG9f0X/vaCLMzX2qzwnGlMLHsXY5rwzmchtvS57qL992hYh/G0BPjO5mr+m87vJuZhX+4dVAc8h7XZZbOTaORVRXKnh7qJ4N0czF/e4bH0Qv6iaMWTEEKLjKmt8N09Zw3AVD9tD7feIgI6Rj9BfCItIcOYS9wYVRMuJBDtssFEXdmyBRGUe6ykCAwEAAQ==" diff --git a/VeriFastPrototype/nsl/runResponder.sh b/VeriFastPrototype/nsl/runResponder.sh new file mode 100755 index 0000000..c7211e4 --- /dev/null +++ b/VeriFastPrototype/nsl/runResponder.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# exit when any command fails +set -e + +SCRIPT_DIR=$(dirname "$0") + +$SCRIPT_DIR/nsl \ + --address "127.0.0.1" \ + --port 3000 \ + --privateKey "MIIJKAIBAAKCAgEAx+KThX2pDDgGcNzmDxBfldrN+v7sWoW3FDtszT0Bb/pqVh5Wa2FI8S+eO3SJgytj2B3Kj9GjGy9UqJNTBAJCcm/xkEVCvVQiSro0id+hTN7zSkIDYiUs01LxjF9eZ71715jeXLiNWQNBTaiLrRuvvhQeRSQ328UzKj5aGklgUxmI/9VWOngmELYdsPET/FdxkM0481a+G4o5D7wRZLxAk/EbeNB0ifi4VB4sysMFGX06330KtmJq3jH/jKIOLxu1+OwLT8CAPM/t/J3Vgerr9KBcnEmyIt91+1OJOAUhQGRx0VOnU3qR3WPRUaDrL/5XeozSd45wK2dFZlLcPQ6CSizVPe0lPNobwiwJ/GS3m5YT2EUlOvKdqRMK8n9I3stsaC3sVcFQRIwPmWqg5B7y4QO1vQfBMyz8rc1kxfwETo5so3IGPzxKQWkF4Az2wdKyljuRApAK5DgF4q7mLiCeGXofuBFp1KqWV5QLUKBxNKy5qCmbv+FarCG9f0X/vaCLMzX2qzwnGlMLHsXY5rwzmchtvS57qL992hYh/G0BPjO5mr+m87vJuZhX+4dVAc8h7XZZbOTaORVRXKnh7qJ4N0czF/e4bH0Qv6iaMWTEEKLjKmt8N09Zw3AVD9tD7feIgI6Rj9BfCItIcOYS9wYVRMuJBDtssFEXdmyBRGUe6ykCAwEAAQKCAgAItrbppd29u5+EQg0BcRxJox1BqOVS2OtvRVvr4pHyeL8z++SWj8onQYUrYFwyTKzwmfPfqyrqH3kYVDvVO+f7pyenu206ZbWM/msV65rTiBChFxmgqLA4kjAXh3zNFvSUJITlE+KNk9e/8+4K4N8bcTMUnoyU1xbw64DwDmzVkpJnXLyClgRKzDWlJg1R/dnkjx3BdfGZr7/nELLNamuCR4uEC8pYzW/zstEPEctiHhR387KI2ud/wjw6vEHUCeg37spbcq/kdsgQMC1DmaMpqef/pDLQ1F8HUs/zkj2t2fMgRf5QZFKBjT1tyo8WK4dSv1rZfWskaT8cQ9Z4Du7dCx728uIgBu2C9xVjipGo6LlyJ+LCaq4Yo3TZTxQhNfVA6+LQD5MFpBjxeaCms0wGYhYtJQ2GkYO2ImSH/52f8r6KeHCMolcESUsuv5feewX76jQMkqYzX6GnEfkWlcleTk5hFRRom42LgWdufzbhhyHf5EqAFdSb9Umf7fUR1CMxnCJzAiE48ObTNMmNyiiAX5vfuwR1GeOf9KJpOULh3g1ilcZEzpCJRqqPktZC8+yeFD2f6GZQlrtM6Gpi2ke6F3b0ewYCW8B3TM+XensWNj+p+DiZpyCdW3q9cug2dtrf/54fKSFXp1sOsdtvh6o/LX9upLi9blxqIvDd6DvfpQKCAQEAzK2zJbgN5ip5ZqH1IFmyVhf/n/owNMffFZt+nMjy4sro9vto3NxlbaSkirX2x7znGutLSwGkq48QydxohpYMHeDs+0Bb2uVqQAgsQtb533YsQtLoCq75olH2WD1KU7rKFCsEzk1P/uroYdzZd5ELI/6I6JUXbYpL8ddYjxWOIIokldZE2nl2qyNfXbE6+D/YY4m4343kXCQXRnE8SrGmt2oV3IS7y1vcXq9zUEP6JtcPjz+9TzaZyjWfiyk6r5jl7KyeeUw3Y2dD0q+/WZ3A5MQw5VUOB1/0KYX/ZFijkAilKS986XAB7OVLMTC62ggSBE5rHrqkSbAEv95jSC756wKCAQEA+gEvZWR/81tLw6wSYb01HEoU61YGZrGPbcF2OJH+5aHdvB4ATUhN8uhDDFmmHBi0RtNLXbUbXDedASSwmTd17dvMxvecsUNFtEiyjh+YYTqJrMLfaLS2XEOV/kums4i2NCn655TLpAokHK6b1vF6n/33AbrolF4mIfVGYeFVeUgi2MBUxGbYa/sXs7aJ1MWPvks19XyWCBF2za+wH8rgQBLjrKrQMS61EHRzTTauwMsAO+SJgB03cAkLMOKGMNOiyXOSrLROCB2EUr6aW+wYmxjFvjG+8e5538YWf+jtencWwnisjRrcnwpYQEvvA3sRSf8te7tDN1s2l9kDmgt2OwKCAQAtboTsY21aYKUv3dU+SAqox4zrIqqenJrs/eXdwVEAfE+3uths5dLxwnDvhTJw5YJa3E2LKaM2nXv5gp4E+btYyntvzbpV3UR3UBkbAQLX0jBC6POuo2Yv3IeU0I32BekjDuVzMYAHMndAebgfrSdO5wnWrnlTzDXNSaKTqBIzMNasF7KS2BE6LZDWiCdxwSIz/fb2UFWXCj/MWAgtAD/kSHzvxNq2af6BWep7r4sQIf6HKnvH4HPEiaCPUCiBn6uxnCNVA1DsFJjeZDpSFw0g+ldsIDQL+QWGTgMBcBdmOjUG7k6Itl8HCWJmWc2v5ciyAgIPARjEbnivahqZhCvfAoIBADxA2t5x+VB6mWkAaLG7uzglNqN9aS+I7cuDC+4YabmIaHt1M5dsrLS1e6tXU+yDm7dSJ2DfTEfOc32aDSHwNvDrv4/Yj6A9WWhY+Qe936jXReUoVlXS7/yOoXDXZMbyVQ9/aqQzvVy8wPVUs+R68JXszIJTPMi9ZC1dAuiGOWZwl01sFUH8k3561ryOauun7bvsPoX6z+ID64EpLaaL674lj0/HH0QrQKJFnqBmZHm8s0K8EtOYtwq+cz8F6VeNOjeZLimHjyLvkjurCmLLJScEMmxjauS+GAtxn2yWg923I/ocwWGErtV51ckxQ9qv53vRD3I5sLp/tkmkmPSgfI8CggEBALJwwX99YSeg35QGeiHjfpzHPeodTnuYaoigKR53PCkOgPmadi8VrxQZUU0HyjEko8RqsEjxuY0X2XRbaslIJQIL6JHun6ImBnun35TrCMCI/B/XjL+NsGnZVp1lNp1FeogmBRi63VtMFi6JvUbMhjjYMxB3wRyYxsY+1dEezv6iQzQanhBL+upOp1/PAymlAyW0Z/fZSX7urpWuefxXcKNnxm4e7moDITpTjGFkzvNVctBcIyVHnEhoi9WvTONumJKnV5OXn26ert1fx55lhENWyRPyz1wijtMtvE4sH3buCtN6vCHmNK/l45G03+jvP129TT2pN4g1TWmSSth46Rk=" \ + --peerPublicKey "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvVMEI16XJOvbU1WjpnP/ne2ndS7zuFMgidUxrSVB6loHpt59ZBfhczjnl0ZkSOvbGmJrbAfqIjHs9oFwmZoTp/hgoJF0bXFbJZQixx79gt3SNwt9cqUvA0851lY00dYkfiqRM/rT9e6C0VAGuIWo/VOpf4zDIrYKwC4DbDQ6e+DyuNqgym+5PnAezo5wwqA05k7TRhq5fBIDuH5LepemMwz0uJmwascsU6KNXFVMrc+e8lu1uiBeELiXH2IliOw0GgoYxx26OLM8+o/uBLoHcxiprR3nXangHsr+3I7LyIZtRyOIycok1/XVj6Nch5bzWX53+7xrSC0P4OncNPIm++O7NOqLAE2kZfAzHrNA2oBu7wUDE4BVnvM9QG0KzTIY9QVQbawNzY+SzmpolkEvJ5AfvdtGQond/vgi8+DLZwyP5CH1mJMiT1TSFlPb8C17FuYdz3mHMgBM6Z/6edVcmeul4gujFcc+umodDpZqw24wdx+t3UStOwCArX/JMbSH9Zu17RdnwiPUK8cYa756rqzScJhIReZuDZgG2jqxWcr3IvJcrllDiwyhB/pELn9OFYRsd0QdHNA16GLwRZejmxJXp5OC9UVzViJ+O4NeW+Sx0tb5o0yIbBEUHY1Kl7Z519svRrXCKASk0EfAyWCcrY38TfyvmMNwspNrh3K/pnMCAwEAAQ==" diff --git a/VeriFastPrototype/nsl/verify.sh b/VeriFastPrototype/nsl/verify.sh new file mode 100755 index 0000000..9fb0921 --- /dev/null +++ b/VeriFastPrototype/nsl/verify.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +# exit when any command fails +set -e + +SCRIPT_DIR=$(dirname "$0") + +VERIFAST="verifast" + +# copy files in this directory and all `.h` files from the library +# to some temporary directory to then verify them: + +# try linux and fall-back to macOS: +TMP_DIR=$(mktemp -d -t verification-XXXXXXXXXX -p "$SCRIPT_DIR/.." 2>/dev/null || mktemp -d "$SCRIPT_DIR/../verification-XXXXXXXXXX") + +# copy reusable verification library: +LIB_FILES=($(find $SCRIPT_DIR/../reusable_verification_library -type f \( -iname \*.gh -o -iname \*.h \))) +for LIB_FILE in "${LIB_FILES[@]}" +do + cp "$LIB_FILE" "$TMP_DIR" +done + +# remove the header files containing dummy definitions as we use the NSL-specific ones instead: +rm "$TMP_DIR/protocol_specific_definitions.gh" +rm "$TMP_DIR/protocol_specific_event_params.gh" + +# copy NSL: +NSL_FILES=($(find $SCRIPT_DIR/ -type f \( -iname \*.gh -o -iname \*.h -o -iname \*.c \))) + +for FILE in "${NSL_FILES[@]}" +do + FILENAME=$(basename "$FILE") + DEST_FILE="$TMP_DIR/$FILENAME" + if [[ -e "$DEST_FILE" ]] + then + echo "File already exists: $FILE" + exit 1 + else + cp "$FILE" "$DEST_FILE" + fi +done + +# verify all `.c` files in the tmp folder except `main.c` and `messages.c`, +# which are trusted and thus should not be verified: +C_FILES=($(find $TMP_DIR -type f \( -iname "*.c" ! -iname "main.c" ! -iname "messages.c" \) | sort)) + +# iterate over each file and verify it: +for FILE in "${C_FILES[@]}" +do + $VERIFAST -allow_dead_code -shared -D PROTOCOL_SPECIFIC_VERIFICATION -c $FILE +done + +rm -r $TMP_DIR diff --git a/VeriFastPrototype/reusable_verification_library/attacker.gh b/VeriFastPrototype/reusable_verification_library/attacker.gh new file mode 100644 index 0000000..0b7ef84 --- /dev/null +++ b/VeriFastPrototype/reusable_verification_library/attacker.gh @@ -0,0 +1,17 @@ +#ifndef ATTACKER +#define ATTACKER + + +#include "term.gh" +#include "trace_entry.gh" +#include "trace_entry_helpers.gh" + +fixpoint list attackerKnowledge(trace tr) { + return append(append(getTermsMadePublic(tr), getMessagePayloads(tr)), getPublicTerms(tr)); +} + +fixpoint bool attackerKnows(trace tr, Term term) { + return mem(term, attackerKnowledge(tr)); +} + +#endif diff --git a/VeriFastPrototype/reusable_verification_library/bytes.gh b/VeriFastPrototype/reusable_verification_library/bytes.gh new file mode 100644 index 0000000..a2911a2 --- /dev/null +++ b/VeriFastPrototype/reusable_verification_library/bytes.gh @@ -0,0 +1,88 @@ +#ifndef BYTES +#define BYTES + +#include "term.gh" + + +/*---- Contains definition for the gamma function that maps terms to bytes -----*/ +fixpoint list gamma(Term t); +fixpoint Term oneTerm(list bytes); + +// constructors +fixpoint list integerB(int i); +fixpoint list stringB(list str); +fixpoint list encryptB(list publicKey, list plaintext); +fixpoint list hashB(list input); +fixpoint list publicKeyB(list b); +fixpoint list tuple2B(list b1, list b2); +fixpoint list tuple3B(list b1, list b2, list b3); +fixpoint list tuple4B(list b1, list b2, list b3, list b4); + +// deconstructors +fixpoint list getFirstB(list b); +fixpoint list getSecondB(list b); +fixpoint list getThirdB(list b); +fixpoint list getForthB(list b); +fixpoint list decryptB(list sk, list plaintext); + +lemma_auto void tuple2BDeconstruction(list b1, list b2); + requires true; + ensures getFirstB(tuple2B(b1, b2)) == b1 && + getSecondB(tuple2B(b1, b2)) == b2; + +lemma_auto void tuple3BDeconstruction(list b1, list b2, list b3); + requires true; + ensures getFirstB(tuple3B(b1, b2, b3)) == b1 && + getSecondB(tuple3B(b1, b2, b3)) == b2 && + getThirdB(tuple3B(b1, b2, b3)) == b3; + +lemma_auto void tuple4BDeconstruction(list b1, list b2, list b3, list b4); + requires true; + ensures getFirstB(tuple4B(b1, b2, b3, b4)) == b1 && + getSecondB(tuple4B(b1, b2, b3, b4)) == b2 && + getThirdB(tuple4B(b1, b2, b3, b4)) == b3 && + getForthB(tuple4B(b1, b2, b3, b4)) == b4; + +// decryption with correct key +lemma_auto void decryptBDeconstruction(list sk, list plaintext); + requires true; + ensures decryptB(sk, encryptB(publicKeyB(sk), plaintext)) == plaintext; + +lemma_auto void totality(list bytes); + requires true; + ensures gamma(oneTerm(bytes)) == bytes; + +// homomorphism +lemma_auto void gammaHomomorphismInteger(int i); + requires true; + ensures gamma(integer(i)) == integerB(i); + +lemma_auto void gammaHomomorphismString(list s); + requires true; + ensures gamma(stringTerm(s)) == stringB(s); + +lemma_auto void gammaHomomorphismEncrypt(Term pk, Term plaintext); + requires true; + ensures gamma(encrypt(pk, plaintext)) == encryptB(gamma(pk), gamma(plaintext)); + +lemma_auto void gammaHomomorphismHash(Term t); + requires true; + ensures gamma(hash(t)) == hashB(gamma(t)); + +lemma_auto void gammaHomomorphismPublicKey(Term t); + requires true; + ensures gamma(publicKey(t)) == publicKeyB(gamma(t)); + +lemma_auto void gammaHomomorphismTuple2(Term t1, Term t2); + requires true; + ensures gamma(tuple2(t1, t2)) == tuple2B(gamma(t1), gamma(t2)); + +lemma_auto void gammaHomomorphismTuple3(Term t1, Term t2, Term t3); + requires true; + ensures gamma(tuple3(t1, t2, t3)) == tuple3B(gamma(t1), gamma(t2), gamma(t3)); + +lemma_auto void gammaHomomorphismTuple4(Term t1, Term t2, Term t3, Term t4); + requires true; + ensures gamma(tuple4(t1, t2, t3, t4)) == tuple4B(gamma(t1), gamma(t2), gamma(t3), gamma(t4)); + +#endif diff --git a/VeriFastPrototype/reusable_verification_library/concurrent_datastructure.c b/VeriFastPrototype/reusable_verification_library/concurrent_datastructure.c new file mode 100644 index 0000000..6a23710 --- /dev/null +++ b/VeriFastPrototype/reusable_verification_library/concurrent_datastructure.c @@ -0,0 +1,317 @@ +#include "concurrent_datastructure.h" +#include + + +/*@ +// `oldGlobalTrace` keeps track of the global trace at the time of acquiring the concurrent +// data structure. This enables us to express a precondition for `release` that checks +// that the global trace has been extended by at most 1 trace entry such that atomicity +// is guaranteed. +predicate oldGlobalTrace(Trace globalTrace) = true; + +// This recursive predicate contains [1/2] permission to each ghost cell contained in `snapshotIds`. +predicate snapshot_perm_forall(SnapIdList snapshotIds, SnapList snapshots) = + switch (snapshotIds) { + case nil: return true; + case cons(id, remIds): + return switch (snapshots) { + case nil: return false; + case cons(snapshot, remSnapshots): + return ghost_cell(id, snapshot) &*& mem(id, snapshotIds) == true &*& snapshot_perm_forall(remIds, remSnapshots); + }; + }; + +// abstraction of permissions and properties needed by a given client (identified by `snapshotId`) to interact +// with the concurrent data structure. +predicate ConcurrentDataStructurePerClientMem(struct ConcurrentDataStructure *cds, SnapId snapshotId, Trace snapshot, PkePred pkePred) = + // note that `f == 1/clientSize` holds but I'm not sure how to make VeriFast's typechecker happy + [?f]cds->size |-> ?clientSize &*& + [f]cds->mutex |-> ?l &*& + [f/2]cds->snapshots |-> ?snapshotIds &*& + distinct(snapshotIds) == true &*& + mem(snapshotId, snapshotIds) == true &*& + [1/2]ghost_cell(snapshotId, snapshot) &*& + [f]mutex(l, mutex_invariant(cds, clientSize, pkePred)) &*& + [f]malloc_block_ConcurrentDataStructure(cds); + +// abstraction of permissions and properties given to a client (identified by `snapshotId`) after acquiring the concurrent +// data structure and that will be necessary to call `release`. +predicate ConcurrentDataStructurePerClientMemLocked(struct ConcurrentDataStructure *cds, SnapId snapshotId, SnapIdList snapshotIds, int threadId, PkePred pkePred) = + // note that `f == 1/clientSize` holds but I'm not sure how to make VeriFast's typechecker happy + [?f]cds->size |-> ?clientSize &*& + [f]cds->mutex |-> ?l &*& + [f/2]cds->snapshots |-> snapshotIds &*& + [1/2]cds->snapshots |-> snapshotIds &*& + distinct(snapshotIds) == true &*& + mem(snapshotId, snapshotIds) == true &*& + mutex_held(l, mutex_invariant(cds, clientSize, pkePred), threadId, f) &*& + [f]malloc_block_ConcurrentDataStructure(cds); + +lemma void snapshotIsSuffix(SnapId snapshotId, SnapIdList snapshotIds, Trace globalTrace) + requires [?f]snapshot_suffix_forall(snapshotIds, globalTrace) &*& + [?g]ghost_cell(snapshotId, ?snapshot) &*& + mem(snapshotId, snapshotIds) == true; + ensures [f]snapshot_suffix_forall(snapshotIds, globalTrace) &*& + [g]ghost_cell(snapshotId, snapshot) &*& + isSuffix(snapshot, globalTrace) == true; +{ + switch (snapshotIds) { + case nil: + case cons(id, remSnapshotIds): + open [f]snapshot_suffix_forall(snapshotIds, globalTrace); + if (id == snapshotId) { + // we perform a proof by contradiction as this saves + // us from having to split the fractions after merging + // them: + if (!isSuffix(snapshot, globalTrace)) { + merge_fractions ghost_cell(snapshotId, _); + assert false; // contradiction -- as expected + } + } else { + snapshotIsSuffix(snapshotId, remSnapshotIds, globalTrace); + } + close [f]snapshot_suffix_forall(snapshotIds, globalTrace); + } +} + +predicate ConcurrentDataStructureMem(struct ConcurrentDataStructure *cds, int clientSize, PkePred pkePred) = + cds->size |-> clientSize &*& + cds->mutex |-> ?l &*& + [1/2]cds->snapshots |-> ?snapshotIds &*& + [1/2]snapshot_perm_forall(snapshotIds, ?snapshots) &*& + clientSize == length(snapshotIds) &*& + clientSize == length(snapshots) &*& + distinct(snapshotIds) == true &*& + mutex(l, mutex_invariant(cds, clientSize, pkePred)) &*& + malloc_block_ConcurrentDataStructure(cds); + +lemma void newGhostCellHasDistinctId(SnapIdList snapshotIds, SnapId newId) + requires [1/2]snapshot_perm_forall(snapshotIds, ?snapshots) &*& + ghost_cell(newId, ?snapshot) &*& + distinct(snapshotIds) == true; + ensures [1/2]snapshot_perm_forall(snapshotIds, snapshots) &*& + ghost_cell(newId, snapshot) &*& + distinct(cons(newId, snapshotIds)) == true; +{ + switch (snapshotIds) { + case nil: + case cons(curSnapshotId, remSnapshotIds): + open [1/2]snapshot_perm_forall(snapshotIds, snapshots); + if (curSnapshotId == newId) { + merge_fractions ghost_cell(curSnapshotId, _); + ghost_cell_fraction_info(curSnapshotId); + assert false; // contradiction -- as expected + } else { + newGhostCellHasDistinctId(remSnapshotIds, newId); + } + close [1/2]snapshot_perm_forall(snapshotIds, snapshots); + } +} +@*/ + +struct ConcurrentDataStructure *allocateConcurrentDataStructure(int clientSize) +/*@ requires exists(?root_terms) &*& + exists(?pkePred) &*& + clientSize > 0 &*& + publicInv(root_terms, root(root_terms), pkePred) == true; @*/ +//@ ensures ConcurrentDataStructureMem(result, clientSize, pkePred); +{ + struct ConcurrentDataStructure *cds = malloc(sizeof(struct ConcurrentDataStructure)); + if (cds == 0){ + abort(); + } + //@ cds->snapshots = nil; + //@ Trace initialTrace = root(root_terms); + //@ SnapId globalTraceId = create_ghost_cell(initialTrace); + //@ cds->ghost_trace = globalTraceId; + cds->size = clientSize; + //@ close valid_trace(initialTrace, pkePred); + + int i = 0; + //@ close [1/2]snapshot_suffix_forall(nil, initialTrace); + //@ close [1/2]snapshot_perm_forall(nil, nil); + + //@ SnapList snapshots = nil; + for (; i< clientSize; i++) + /*@ invariant cds->snapshots |-> ?snapshotIds &*& + [1/2]snapshot_perm_forall(snapshotIds, snapshots) &*& + i == length(snapshotIds) &*& + i == length(snapshots) &*& + i <= clientSize &*& + [1/2]snapshot_suffix_forall(snapshotIds, initialTrace) &*& + distinct(snapshotIds) == true; @*/ + { + //@ SnapId nextSnapshotId = create_ghost_cell(initialTrace); + //@ newGhostCellHasDistinctId(snapshotIds, nextSnapshotId); + //@ SnapIdList newSnapshotIds = cons(nextSnapshotId, snapshotIds); + //@ cds->snapshots = newSnapshotIds; + //@ snapshots = cons(initialTrace, snapshots); + //@ close [1/2]snapshot_perm_forall(newSnapshotIds, snapshots); + //@ close [1/2]snapshot_suffix_forall(newSnapshotIds, initialTrace); + } + + //@ close mutex_invariant(cds, clientSize, pkePred)(); + //@ close create_mutex_ghost_arg(mutex_invariant(cds,clientSize, pkePred)); + + cds->mutex = create_mutex(); + + //@ close ConcurrentDataStructureMem(cds, clientSize, pkePred); + + return cds; +} + +/*@ +lemma void convertConcurrentDataStructureMemHelper(struct ConcurrentDataStructure *cds, SnapIdList curSnapIds, SnapList curSnaps, real f) + requires length(curSnapIds) == length(curSnaps) &*& + [f]cds->size |-> ?clientSize &*& + [f]cds->mutex |-> ?l &*& + [f/2]cds->snapshots |-> ?snapshotIds &*& + distinct(snapshotIds) == true &*& + subset(curSnapIds, snapshotIds) == true &*& + [1/2]snapshot_perm_forall(curSnapIds, curSnaps) &*& + exists(?pkePred) &*& + [f]mutex(l, mutex_invariant(cds, clientSize, pkePred)) &*& + [f]malloc_block_ConcurrentDataStructure(cds); + ensures ConcurrentDataStructureAllClientMem(cds, curSnapIds, curSnaps, pkePred); +{ + switch (curSnapIds) { + case nil: + close ConcurrentDataStructureAllClientMem(cds, curSnapIds, curSnaps, pkePred); + open [1/2]snapshot_perm_forall(curSnapIds, curSnaps); + // we can unfortunately not compute a precise fraction as VeriFast + // does not allow us to express that the permission fraction `f` within + // the `ConcurrentDataStructurePerClientMem` predicate is `1/clientSize`. + leak [_]cds->size |-> clientSize; + leak [_]cds->mutex |-> l; + leak [_]cds->snapshots |-> snapshotIds; + leak [_]mutex(l, mutex_invariant(cds, clientSize, pkePred)); + leak [_]malloc_block_ConcurrentDataStructure(cds); + case cons(id, remIds): + switch (curSnaps) { + case nil: // this case cannot occur + case cons(snapshot, remSnapshots): + open [1/2]snapshot_perm_forall(curSnapIds, curSnaps); + convertConcurrentDataStructureMemHelper(cds, remIds, remSnapshots, f/2); + close ConcurrentDataStructurePerClientMem(cds, id, snapshot, pkePred); + close ConcurrentDataStructureAllClientMem(cds, curSnapIds, curSnaps, pkePred); + } + } +} + +lemma void convertConcurrentDataStructureMem(struct ConcurrentDataStructure *cds, PkePred pkePred) + requires ConcurrentDataStructureMem(cds, ?clientSize, pkePred); + ensures exists(?snapshotIds) &*& + exists(?snapshots) &*& + clientSize == length(snapshotIds) &*& + clientSize == length(snapshots) &*& + ConcurrentDataStructureAllClientMem(cds, snapshotIds, snapshots, pkePred); +{ + open ConcurrentDataStructureMem(cds, clientSize, pkePred); + assert [1/2]cds->snapshots |-> ?snapshotIds; + assert [1/2]snapshot_perm_forall(snapshotIds, ?snapshots); + close exists(pkePred); + convertConcurrentDataStructureMemHelper(cds, snapshotIds, snapshots, 1/1); + close exists(snapshotIds); + close exists(snapshots); +} +@*/ + +struct ConcurrentDataStructure *createConcurrentDataStructure(int clientSize) +/*@ requires exists(?root_terms) &*& + exists(?pkePred) &*& + clientSize > 0 &*& + publicInv(root_terms, root(root_terms), pkePred) == true; @*/ +/*@ ensures exists(?snapshotIds) &*& + exists(?snapshots) &*& + clientSize == length(snapshotIds) &*& + clientSize == length(snapshots) &*& + ConcurrentDataStructureAllClientMem(result, snapshotIds, snapshots, pkePred); @*/ +{ + struct ConcurrentDataStructure *cds = allocateConcurrentDataStructure(clientSize); + //@ convertConcurrentDataStructureMem(cds, pkePred); + return cds; +} + +void acquire(struct ConcurrentDataStructure *cds) +//@ requires ConcurrentDataStructurePerClientMem(cds, ?snapshotId, ?snapshot, ?pkePred); +/*@ ensures ConcurrentDataStructurePerClientMemLocked(cds, snapshotId, ?snapshotIds, currentThread, pkePred) &*& + locked_properties(cds, snapshotId, snapshot, snapshotIds, ?globalTrace, pkePred) &*& + oldGlobalTrace(globalTrace); @*/ +{ + //@ open ConcurrentDataStructurePerClientMem(cds, snapshotId, snapshot, pkePred); + mutex_acquire(cds->mutex); + //@ assert [_]cds->size |-> ?clientSize; + //@ open mutex_invariant(cds, clientSize, pkePred)(); + //@ assert cds->ghost_trace |-> ?globalTraceId; + //@ assert ghost_cell(globalTraceId, ?globalTrace); + //@ assert [1/2]snapshot_suffix_forall(?snapshotIds, globalTrace); + //@ close ConcurrentDataStructurePerClientMemLocked(cds, snapshotId, snapshotIds, currentThread, pkePred); + //@ close oldGlobalTrace(globalTrace); + //@ snapshotIsSuffix(snapshotId, snapshotIds, globalTrace); + //@ close locked_properties(cds, snapshotId, snapshot, snapshotIds, globalTrace, pkePred); +} + +void release(struct ConcurrentDataStructure *cds) +/*@ requires ConcurrentDataStructurePerClientMemLocked(cds, ?snapshotId, ?snapshotIds, currentThread, ?pkePred) &*& + locked_properties(cds, snapshotId, ?snapshot, snapshotIds, ?globalTrace, pkePred) &*& + oldGlobalTrace(?oldGlobalTrace) &*& + // the following precondition expresses that at most one trace entry is appended such that + // atomicity is guaranteed. + (globalTrace == oldGlobalTrace || getPrev(globalTrace) == oldGlobalTrace); @*/ +//@ ensures ConcurrentDataStructurePerClientMem(cds, snapshotId, snapshot, pkePred); +{ + //@ open ConcurrentDataStructurePerClientMemLocked(cds, snapshotId, snapshotIds, currentThread, pkePred); + //@ open locked_properties(cds, snapshotId, snapshot, snapshotIds, globalTrace, pkePred); + //@ assert [_]cds->size |-> ?clientSize; + //@ close mutex_invariant(cds, clientSize, pkePred)(); + mutex_release(cds->mutex); + //@ open oldGlobalTrace(oldGlobalTrace); + //@ close ConcurrentDataStructurePerClientMem(cds, snapshotId, snapshot, pkePred); +} + +/*@ +lemma void snapshot_suffix_hold_event(trace globalTrace, SnapIdList snapshotIds) + requires [?f]snapshot_suffix_forall(snapshotIds, ?oldTrace) &*& globalTrace == makeEvent(oldTrace, _,_); + ensures [f]snapshot_suffix_forall(snapshotIds, globalTrace); +{ + switch (snapshotIds) { + case nil: + open [f]snapshot_suffix_forall(snapshotIds, oldTrace); + close [f]snapshot_suffix_forall(snapshotIds, globalTrace); + case cons(id, remIds): + open [f]snapshot_suffix_forall(snapshotIds, oldTrace); + snapshot_suffix_hold_event(globalTrace, remIds); + close [f]snapshot_suffix_forall(snapshotIds, globalTrace); + } +} + +lemma void snapshot_suffix_hold_message(trace globalTrace, SnapIdList snapshotIds) + requires [?f]snapshot_suffix_forall(snapshotIds, ?oldTrace) &*& globalTrace == makeMessage(oldTrace, _,_,_); + ensures [f]snapshot_suffix_forall(snapshotIds, globalTrace); +{ + switch (snapshotIds) { + case nil: + open [f]snapshot_suffix_forall(snapshotIds, oldTrace); + close [f]snapshot_suffix_forall(snapshotIds, globalTrace); + case cons(id, remIds) : + open [f]snapshot_suffix_forall(snapshotIds, oldTrace); + snapshot_suffix_hold_message(globalTrace, remIds); + close [f]snapshot_suffix_forall(snapshotIds, globalTrace); + } +} + +lemma void snapshot_suffix_hold_nonce(trace globalTrace, SnapIdList snapshotIds) + requires [?f]snapshot_suffix_forall(snapshotIds, ?oldTrace) &*& globalTrace == makeNonce(oldTrace, _); + ensures [f]snapshot_suffix_forall(snapshotIds, globalTrace); +{ + switch (snapshotIds) { + case nil: + open [f]snapshot_suffix_forall(snapshotIds, oldTrace); + close [f]snapshot_suffix_forall(snapshotIds, globalTrace); + case cons(id, remIds): + open [f]snapshot_suffix_forall(snapshotIds, oldTrace); + snapshot_suffix_hold_nonce(globalTrace, remIds); + close [f]snapshot_suffix_forall(snapshotIds, globalTrace); + } +} +@*/ diff --git a/VeriFastPrototype/reusable_verification_library/concurrent_datastructure.h b/VeriFastPrototype/reusable_verification_library/concurrent_datastructure.h new file mode 100644 index 0000000..c9a4663 --- /dev/null +++ b/VeriFastPrototype/reusable_verification_library/concurrent_datastructure.h @@ -0,0 +1,136 @@ +#ifndef CONCURRENT_DATASTRUCTURE +#define CONCURRENT_DATASTRUCTURE + +//@#include "ghost_cells.gh" +#include "threading.h" +//@ #include "trace_entry.gh" +//@ #include "trace_invariant.gh" + + +#define SnapId int +#define SnapIdList list +#define SnapList list +#define TermList list + + +struct ConcurrentDataStructure { + int size; + struct mutex *mutex; + //@ SnapIdList snapshots; + //@ SnapId ghost_trace; +}; + +// On a high level, a single (globally shared) instance of the concurrent data structure +// should be created by calling `createConcurrentDataStructure` and specifying by how many +// clients this instance will be shared. +// This returns an instance of the `ConcurrentDataStructureAllClientMem` predicate, which +// contains one instance of the `ConcurrentDataStructurePerClientMem` predicate per specified +// client. Thus, a caller should distribute the predicate instances such that each client, i.e. +// protocol session, gets its `ConcurrentDataStructurePerClientMem` predicate instance. +// This predicate instance is then sufficient to interact with the concurrent data structure +// via calls to `acquire` and `release`. + +/*@ +// `oldGlobalTrace` keeps track of the global trace at the time of acquiring the concurrent +// data structure. This enables us to express a precondition for `release` that checks +// that the global trace has been extended by at most 1 trace entry such that atomicity +// is guaranteed. +predicate oldGlobalTrace(Trace globalTrace); + +// This recursive predicate contains permission to each ghost cell storing a snapshot +// and ensures that each snapshot is a suffix of the global trace. +predicate snapshot_suffix_forall(SnapIdList snapshotIds, Trace globalTrace) = + switch (snapshotIds) { + case nil: return true; + case cons(id, remIds): + return ghost_cell(id, ?snapshot) &*& + isSuffix(snapshot, globalTrace) == true &*& + snapshot_suffix_forall(remIds, globalTrace); + }; + +// This recursive predicate contains [1/2] permission to each ghost cell contained in `snapshotIds`. +predicate snapshot_perm_forall(SnapIdList snapshotIds, SnapList snapshots); + +// invariant of the mutex that contains full access to the ghost cell storing the global trace and [1/2] permissions +// to each ghost cell storing a snapshot. +predicate_ctor mutex_invariant(struct ConcurrentDataStructure *cds, int size, PkePred pkePred)() = + cds->ghost_trace |-> ?globalTraceId &*& [1/2]cds->snapshots |-> ?snapshotIds &*& ghost_cell(globalTraceId, ?globalTrace) &*& valid_trace(globalTrace, pkePred) + &*& [1/2]snapshot_suffix_forall(snapshotIds, globalTrace); + +// abstraction of permissions and properties needed by a given client (identified by `snapshotId`) to interact +// with the concurrent data structure. +predicate ConcurrentDataStructurePerClientMem(struct ConcurrentDataStructure *cds, SnapId snapshotId, Trace snapshot, PkePred pkePred); + +// abstracts over the permissions for each client, i.e., `ConcurrentDataStructurePerClientMem`. +predicate ConcurrentDataStructureAllClientMem(struct ConcurrentDataStructure *cds, SnapIdList snapshotIds, SnapList snapshots, PkePred pkePred) = + switch (snapshotIds) { + case nil: return true; + case cons(id, remIds): + return switch (snapshots) { + case nil: return false; // express that `length(snapshotIds) <= length(snapshots)` + case cons(snapshot, remSnapshots): + return ConcurrentDataStructurePerClientMem(cds, id, snapshot, pkePred) &*& + ConcurrentDataStructureAllClientMem(cds, remIds, remSnapshots, pkePred); + }; + }; + +// abstraction of permissions and properties given to a client (identified by `snapshotId`) after acquiring the concurrent +// data structure and that will be necessary to call `release`. +predicate ConcurrentDataStructurePerClientMemLocked(struct ConcurrentDataStructure *cds, SnapId snapshotId, SnapIdList snapshotIds, int threadId, PkePred pkePred); + +// abstraction of permissions and properties that a client obtains after acquiring the concurrent data structure and +// that are useful to interact and/or update the concurrent data structure's state. +predicate locked_properties(struct ConcurrentDataStructure *cds, SnapId snapshotId, Trace snapshot, SnapIdList snapshotIds, Trace globalTrace, PkePred pkePred) = + cds->ghost_trace |-> ?globalTraceId &*& + ghost_cell(globalTraceId, globalTrace) &*& + valid_trace(globalTrace, pkePred) &*& + [1/2]ghost_cell(snapshotId, snapshot) &*& + [1/2]snapshot_suffix_forall(snapshotIds, globalTrace) &*& + mem(snapshotId, snapshotIds) == true &*& + isSuffix(snapshot, globalTrace) == true; +@*/ + +// Creates an ConcurrentStructure Counter and return the permission +struct ConcurrentDataStructure *createConcurrentDataStructure(int clientSize); +/*@ requires exists(?root_terms) &*& + exists(?pkePred) &*& + clientSize > 0 &*& + publicInv(root_terms, root(root_terms), pkePred) == true; @*/ +/*@ ensures exists(?snapshotIds) &*& + exists(?snapshots) &*& + clientSize == length(snapshotIds) &*& + clientSize == length(snapshots) &*& + ConcurrentDataStructureAllClientMem(result, snapshotIds, snapshots, pkePred); @*/ + +void acquire(struct ConcurrentDataStructure *cds); +//@ requires ConcurrentDataStructurePerClientMem(cds, ?snapshotId, ?snapshot, ?pkePred); +/*@ ensures ConcurrentDataStructurePerClientMemLocked(cds, snapshotId, ?snapshotIds, currentThread, pkePred) &*& + locked_properties(cds, snapshotId, snapshot, snapshotIds, ?globalTrace, pkePred) &*& + oldGlobalTrace(globalTrace); @*/ + +void release(struct ConcurrentDataStructure *cds); +/*@ requires ConcurrentDataStructurePerClientMemLocked(cds, ?snapshotId, ?snapshotIds, currentThread, ?pkePred) &*& + locked_properties(cds, snapshotId, ?snapshot, snapshotIds, ?globalTrace, pkePred) &*& + oldGlobalTrace(?oldGlobalTrace) &*& + // the following precondition expresses that at most one trace entry is appended such that + // atomicity is guaranteed. + (globalTrace == oldGlobalTrace || getPrev(globalTrace) == oldGlobalTrace); @*/ +//@ ensures ConcurrentDataStructurePerClientMem(cds, snapshotId, snapshot, pkePred); + +/*@ +// adding an event to global trace preserves suffix +lemma void snapshot_suffix_hold_event(Trace globalTrace, SnapIdList snapshotIds); + requires [?f]snapshot_suffix_forall(snapshotIds, ?oldTrace) &*& globalTrace == makeEvent(oldTrace, _,_); + ensures [f]snapshot_suffix_forall(snapshotIds, globalTrace); + +// adding a message to global trace preserves suffix +lemma void snapshot_suffix_hold_message(Trace globalTrace, SnapIdList snapshotIds); + requires [?f]snapshot_suffix_forall(snapshotIds, ?oldTrace) &*& globalTrace == makeMessage(oldTrace, _,_,_); + ensures [f]snapshot_suffix_forall(snapshotIds, globalTrace); + +// adding a nonce to global trace preserves suffix +lemma void snapshot_suffix_hold_nonce(Trace globalTrace, SnapIdList snapshotIds); + requires [?f]snapshot_suffix_forall(snapshotIds, ?oldTrace) &*& globalTrace == makeNonce(oldTrace, _); + ensures [f]snapshot_suffix_forall(snapshotIds, globalTrace); +@*/ +#endif diff --git a/VeriFastPrototype/reusable_verification_library/crypto.c b/VeriFastPrototype/reusable_verification_library/crypto.c new file mode 100644 index 0000000..fb11102 --- /dev/null +++ b/VeriFastPrototype/reusable_verification_library/crypto.c @@ -0,0 +1,334 @@ +#include "crypto.h" +#include +#include +#include + +// note that implementations in this file are trusted, i.e., not verified. + +char *createNonce() +{ + unsigned char *res = malloc(NonceLength); + if (res == NULL) { + return NULL; + } + int success = RAND_bytes(res, NonceLength); + if (success != 1) { + free(res); + return NULL; + } + return (char *)res; +} + +struct keypair *generatePkeKey() +{ + struct keypair *res = malloc(sizeof(struct keypair)); + if (res == NULL) { + return NULL; + } + + EVP_PKEY *pkey = EVP_RSA_gen(4096); + if (pkey == NULL) { + free(res); + return NULL; + } + + // compute length and allocate corresponding memory for secret key + int sk_len = i2d_PrivateKey(pkey, NULL); + unsigned char *sk = malloc(sk_len); + if (sk == NULL) { + free(res); + EVP_PKEY_free(pkey); + return NULL; + } + // extract secret key + unsigned char *tmp = sk; + int bytes_written = i2d_PrivateKey(pkey, &tmp); // because `tmp` will be modified + if (bytes_written != sk_len) { + free(res); + EVP_PKEY_free(pkey); + free(sk); + return NULL; + } + + // compute length and allocate corresponding memory for public key + int pk_len = i2d_PublicKey(pkey, NULL); + unsigned char *pk = malloc(pk_len); + if (pk == NULL) { + free(res); + EVP_PKEY_free(pkey); + free(sk); + return NULL; + } + // extract public key + tmp = pk; + bytes_written = i2d_PublicKey(pkey, &tmp); // because `tmp` will be modified + if (bytes_written != pk_len) { + free(res); + EVP_PKEY_free(pkey); + free(sk); + free(pk); + return NULL; + } + + EVP_PKEY_free(pkey); + res->pk = (char *)pk; + res->pk_len = pk_len; + res->sk = (char *)sk; + res->sk_len = sk_len; + return res; +} + +// if result is non-NULL, it's a zero-terminated string of +// length (not counting the zero terminator) `(len + 2)/3*4 + 1` +char *base64Encode(const unsigned char *data, const unsigned int len) { + unsigned int out_len = (len + 2)/3*4 + 1; // incl. padding to a number divisible by 4 + printf("len: %d, out_len: %d\n", len, out_len); + char* res = malloc(out_len); + if (res == NULL) { + return NULL; + } + int bytes_written = EVP_EncodeBlock((unsigned char *)res, data, len); + if (bytes_written != out_len - 1) { // bytes_written does not include the NUL terminator + free(res); + return NULL; + } + return res; +} + +// if result is non-NULL, it's a pointer to a buffer of size `strlen(str)/4*3`. +unsigned char *base64Decode(const char *str) { + unsigned char* res = malloc(strlen(str)/4*3); + if (res == NULL) { + return NULL; + } + int bytes_written = EVP_DecodeBlock(res, (unsigned char *)str, strlen(str)); + if (bytes_written != strlen(str)/4*3) { + free(res); + return NULL; + } + return res; +} + +/** + * extracts an RSA private key (PKCS #1, ASN.1 DER format) from a + * a base64 encoded string to a PKCS#8 unencrypted PrivateKeyInfo format. + * The result can be easily turned into a EVP_PKEY by calling `i2d_PrivateKey`. + */ +char *parsePKCS1PrivateKey(const char *str, unsigned int *sk_len) { + unsigned char* decoded_bytes = base64Decode(str); + if (decoded_bytes == NULL) { + return NULL; + } + + EVP_PKEY *pkey = NULL; + const char *format = "DER"; + const char *structure = "type-specific"; + const char *keytype = "RSA"; + OSSL_DECODER_CTX *ctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, format, structure, + keytype, + OSSL_KEYMGMT_SELECT_KEYPAIR, + NULL, NULL); + if (ctx == NULL) { + free(decoded_bytes); + return NULL; + } + const unsigned char *const_decoded_bytes = decoded_bytes; + size_t len = strlen(str)/4*3; + int success = OSSL_DECODER_from_data(ctx, &const_decoded_bytes, &len); + free(decoded_bytes); + if (success != 1) { + OSSL_DECODER_CTX_free(ctx); + return NULL; + } + OSSL_DECODER_CTX_free(ctx); + if (pkey == NULL) { + return NULL; + } + // first compute length: + *sk_len = i2d_PrivateKey(pkey, NULL); + unsigned char *sk = malloc(*sk_len); + if (sk == NULL) { + EVP_PKEY_free(pkey); + return NULL; + } + unsigned char *tmp = sk; + int bytes_written = i2d_PrivateKey(pkey, &tmp); // because `tmp` will be modified + EVP_PKEY_free(pkey); + if (bytes_written != *sk_len) { + free(sk); + return NULL; + } + return (char *)sk; +} + +char *getPublicKey(char *sk, unsigned int sk_len, unsigned int *pk_len) +{ + const unsigned char *u_sk = (const unsigned char *)sk; + EVP_PKEY *pkey = d2i_PrivateKey(EVP_PKEY_RSA, NULL, &u_sk, sk_len); + if (pkey == NULL) { + return NULL; + } + // first compute length: + *pk_len = i2d_PublicKey(pkey, NULL); + unsigned char *pk = malloc(*pk_len); + if (pk == NULL) { + EVP_PKEY_free(pkey); + return NULL; + } + unsigned char *tmp = pk; + int bytes_written = i2d_PublicKey(pkey, &tmp); // because `tmp` will be modified + EVP_PKEY_free(pkey); + if (bytes_written != *pk_len) { + free(pk); + return NULL; + } + return (char *)pk; +} + +/** + * extracts an RSA public key (PKIX, ASN.1 DER format) from a + * a base64 encoded string to a PKCS#8 unencrypted SubjectPublicKeyInfo format. + * The result can be easily turned into a EVP_PKEY by calling `i2d_PublicKey`. + */ +char *parsePKIXPublicKey(const char *str, unsigned int *pk_len) { + unsigned char* decoded_bytes = base64Decode(str); + if (decoded_bytes == NULL) { + return NULL; + } + const unsigned char *const_decoded_bytes = decoded_bytes; + EVP_PKEY *pkey = d2i_PUBKEY(NULL, &const_decoded_bytes, strlen(str)/4*3); + free(decoded_bytes); + if (pkey == NULL) { + return NULL; + } + // first compute length: + *pk_len = i2d_PublicKey(pkey, NULL); + unsigned char *pk = malloc(*pk_len); + if (pk == NULL) { + EVP_PKEY_free(pkey); + return NULL; + } + unsigned char *tmp = pk; + int bytes_written = i2d_PublicKey(pkey, &tmp); // because `tmp` will be modified + EVP_PKEY_free(pkey); + if (bytes_written != *pk_len) { + free(pk); + return NULL; + } + return (char *)pk; +} + +char *enc(char *msg, unsigned int msg_len, const char *pk, unsigned int pk_len, unsigned int *ciphertext_len) +{ + const unsigned char *u_pk = (const unsigned char *)pk; + EVP_PKEY *pkey = d2i_PublicKey(EVP_PKEY_RSA, NULL, &u_pk, pk_len); + if (pkey == NULL) { + return NULL; + } + + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + if (ctx == NULL) { + EVP_PKEY_free(pkey); + return NULL; + } + + int success = EVP_PKEY_encrypt_init(ctx); + if (success != 1) { + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(pkey); + return NULL; + } + + success = EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING); + if (success != 1) { + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(pkey); + return NULL; + } + + size_t len = 0; + success = EVP_PKEY_encrypt(ctx, NULL, &len, (const unsigned char *)msg, msg_len); + if (success != 1) { + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(pkey); + return NULL; + } + + unsigned char *ciphertext = malloc(len); + if (ciphertext == NULL) { + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(pkey); + return NULL; + } + + success = EVP_PKEY_encrypt(ctx, ciphertext, &len, (const unsigned char *)msg, msg_len); + EVP_PKEY_CTX_free(ctx); + if (success != 1) { + EVP_PKEY_free(pkey); + free(ciphertext); + return NULL; + } + + *ciphertext_len = len; + return (char *)ciphertext; +} + +char *dec(char *ciphertext, unsigned int ciphertext_len, const char *sk, unsigned int sk_len, unsigned int *plaintext_len) +//@ requires [?f]chars(ciphertext, ?ciphertextSize, ?ciphertext_bytes) &*& [?g]chars(sk, SkLength, ?sk_bytes); +/*@ ensures [f]chars(ciphertext, ciphertextSize, ciphertext_bytes) &*& [g]chars(sk, SkLength, sk_bytes) &*& + result != 0 ? (chars(result, ?msgSize, ?msg_bytes) &*& + malloc_block_chars(result, msgSize) &*& + ciphertext_bytes == encryptB(publicKeyB(sk_bytes), msg_bytes)) : + emp; @*/ +{ + const unsigned char *u_sk = (const unsigned char *)sk; + EVP_PKEY *pkey = d2i_PrivateKey(EVP_PKEY_RSA, NULL, &u_sk, sk_len); + if (pkey == NULL) { + return NULL; + } + + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + if (ctx == NULL) { + EVP_PKEY_free(pkey); + return NULL; + } + + int success = EVP_PKEY_decrypt_init(ctx); + if (success != 1) { + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(pkey); + return NULL; + } + + success = EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING); + if (success != 1) { + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(pkey); + return NULL; + } + + size_t len = 0; + success = EVP_PKEY_decrypt(ctx, NULL, &len, (const unsigned char *)ciphertext, ciphertext_len); + if (success != 1) { + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(pkey); + return NULL; + } + + unsigned char *plaintext = malloc(len); + if (plaintext == NULL) { + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(pkey); + return NULL; + } + success = EVP_PKEY_decrypt(ctx, plaintext, &len, (const unsigned char *)ciphertext, ciphertext_len); + EVP_PKEY_CTX_free(ctx); + if (success != 1) { + EVP_PKEY_free(pkey); + free(plaintext); + return NULL; + } + + *plaintext_len = len; + return (char *)plaintext; +} diff --git a/VeriFastPrototype/reusable_verification_library/crypto.h b/VeriFastPrototype/reusable_verification_library/crypto.h new file mode 100644 index 0000000..d3e148f --- /dev/null +++ b/VeriFastPrototype/reusable_verification_library/crypto.h @@ -0,0 +1,98 @@ +#ifndef CRYPTO +#define CRYPTO +#define NonceLength 24 + +//@ #include "bytes.gh" +//@ #include "event.gh" +//@ #include "term.gh" +//@ #include "trace_invariant.gh" + + +#define EventTypes list + +// Provides cryptographic functions whose specification follows our perfect cryptography assumptions + +// returns a recursive predicate with a unique resources for every unique event. We add it +// as a recursive predicate since quantified permissions are not supported. +/*@ +predicate eventUniquePointer(EventTypes event_types, Term nonce) = + switch (event_types) { + case nil: + return true; + case cons(event_type, xs): + return EventUniqueResource(nonce, event_type) &*& eventUniquePointer(xs, nonce); + }; +@*/ + +// in the following, we axiomatize functions typically provided by a cryptographic library. + +char *createNonce(); +//@ requires exists(?eventTypes) &*& exists