├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── README.md ├── final_product └── .keep ├── functionary_bob ├── bob └── bob.pub ├── functionary_carl ├── carl └── carl.pub ├── owner_alice ├── alice ├── alice.pub └── create_layout.py ├── requirements.txt ├── run_demo.py └── run_demo_md.py /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | open-pull-requests-limit: 10 9 | 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: "daily" 14 | time: "10:00" 15 | open-pull-requests-limit: 10 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | test: 15 | name: Test 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | python-version: ['3.8', '3.9', '3.10', '3.11'] 20 | os: [ubuntu-latest] 21 | include: 22 | - python-version: "3.11" 23 | os: macos-latest 24 | 25 | runs-on: ${{ matrix.os }} 26 | steps: 27 | - name: Checkout demo 28 | uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 29 | 30 | - name: Set up Python ${{ matrix.python-version }} 31 | uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 32 | with: 33 | python-version: ${{ matrix.python-version }} 34 | cache: 'pip' 35 | cache-dependency-path: 'requirements.txt' 36 | 37 | - name: Install dependencies 38 | run: | 39 | python3 -m pip install -U pip 40 | python3 -m pip install -r requirements.txt 41 | 42 | - name: Run demo 43 | run: | 44 | python3 run_demo_md.py 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # in-toto demo [![CI](https://github.com/in-toto/demo/actions/workflows/ci.yml/badge.svg)](https://github.com/in-toto/demo/actions/workflows/ci.yml) 2 | 3 | In this demo, we will use in-toto to secure a software supply chain with a very 4 | simple workflow. Bob is a developer for a project, Carl packages the software, and 5 | Alice oversees the project. So, using in-toto's names for the parties, 6 | Alice is the project owner - she creates and signs the software supply chain 7 | layout with her private key - and Bob and Carl are project functionaries - 8 | they carry out the steps of the software supply chain as defined in the layout. 9 | 10 | For the sake of demonstrating in-toto, we will have you run all parts of the 11 | software supply chain. 12 | This is, you will perform the commands on behalf of Alice, Bob and Carl as well 13 | as the client who verifies the final product. 14 | 15 | 16 | ## Download and setup in-toto on \*NIX (Linux, OS X, ..) 17 | __Virtual Environments (optional)__ 18 | 19 | We highly recommend installing `in-toto` and its dependencies in a 20 | [`venv`](https://docs.python.org/3/library/venv.html) Python virtual 21 | environment. Just copy-paste the following snippet to create a virtual 22 | environment: 23 | 24 | ```bash 25 | # Create the virtual environment 26 | python -m venv in-toto-demo 27 | 28 | # Activate the virtual environment 29 | # This will add the prefix "(in-toto-demo)" to your shell prompt 30 | source in-toto-demo/bin/activate 31 | ``` 32 | 33 | __Get demo files and install in-toto__ 34 | ```bash 35 | # Fetch the demo repo using git 36 | git clone https://github.com/in-toto/demo.git 37 | 38 | # Change into the demo directory 39 | cd demo 40 | 41 | # Install a compatible version of in-toto 42 | pip install -r requirements.txt 43 | ``` 44 | *Note: If you are having troubles installing in-toto, make sure you have all 45 | the system dependencies. See the [installation guide on 46 | in-toto.readthedocs.io](https://in-toto.readthedocs.io/en/latest/installing.html) 47 | for details.* 48 | 49 | Inside the demo directory you will find four directories: `owner_alice`, 50 | `functionary_bob`, `functionary_carl` and `final_product`. Alice, Bob and Carl 51 | already have RSA keys in each of their directories. This is what you see: 52 | ```bash 53 | tree # If you don't have tree, try 'find .' instead 54 | # the tree command gives you the following output 55 | # . 56 | # ├── README.md 57 | # ├── final_product 58 | # ├── functionary_bob 59 | # │ ├── bob 60 | # │ └── bob.pub 61 | # ├── functionary_carl 62 | # │ ├── carl 63 | # │ └── carl.pub 64 | # ├── owner_alice 65 | # │ ├── alice 66 | # │ ├── alice.pub 67 | # │ └── create_layout.py 68 | # ├── requirements.txt 69 | # ├── run_demo.py 70 | # └── run_demo_md.py 71 | ``` 72 | 73 | ## Run the demo commands 74 | Note: if you don't want to type or copy & paste commands and would rather watch 75 | a script run through the commands, jump to [the last section of this document](#tired-of-copy-pasting-commands) 76 | 77 | ### Define software supply chain layout (Alice) 78 | First, we will need to define the software supply chain layout. To simplify this 79 | process, we provide a script that generates a simple layout for the purpose of 80 | the demo. 81 | 82 | In this software supply chain layout, we have Alice, who is the project 83 | owner that creates the layout, Bob, who clones the project's repo and 84 | performs some pre-packaging editing (update version number), and Carl, who uses 85 | `tar` to package the project sources into a tarball, which 86 | together with the in-toto metadata composes the final product that will 87 | eventually be installed and verified by the end user. 88 | 89 | ```shell 90 | # Create and sign the software supply chain layout on behalf of Alice 91 | cd owner_alice 92 | python create_layout.py 93 | ``` 94 | The script will create a layout, add Bob's and Carl's public keys (fetched from 95 | their directories), sign it with Alice's private key and dump it to `root.layout`. 96 | In `root.layout`, you will find that (besides the signature and other information) 97 | there are three steps, `clone`, `update-version` and `package`, that 98 | the functionaries Bob and Carl, identified by their public keys, are authorized 99 | to perform. 100 | 101 | ### Clone project source code (Bob) 102 | Now, we will take the role of the functionary Bob and perform the step 103 | `clone` on his behalf, that is we use in-toto to clone the project repo from GitHub and 104 | record metadata for what we do. Execute the following commands to change to Bob's 105 | directory and perform the step. 106 | 107 | ```shell 108 | cd ../functionary_bob 109 | in-toto-run --step-name clone --use-dsse --products demo-project/foo.py --signing-key bob -- git clone https://github.com/in-toto/demo-project.git 110 | ``` 111 | 112 | Here is what happens behind the scenes: 113 | 1. In-toto wraps the command `git clone https://github.com/in-toto/demo-project.git`, 114 | 1. hashes the contents of the source code, i.e. `demo-project/foo.py`, 115 | 1. adds the hash together with other information to a metadata file, 116 | 1. signs the metadata with Bob's private key, and 117 | 1. stores everything to `clone.[Bob's keyid].link`. 118 | 119 | ### Update version number (Bob) 120 | Before Carl packages the source code, Bob will update 121 | a version number hard-coded into `foo.py`. He does this using the `in-toto-record` command, 122 | which produces the same link metadata file as above but does not require Bob to wrap his action in a single command. 123 | So first Bob records the state of the files he will modify: 124 | 125 | ```shell 126 | # In functionary_bob directory 127 | in-toto-record start --step-name update-version --use-dsse --signing-key bob --materials demo-project/foo.py 128 | ``` 129 | 130 | Then Bob uses an editor of his choice to update the version number in `demo-project/foo.py`, e.g.: 131 | 132 | ```shell 133 | sed -i.bak 's/v0/v1/' demo-project/foo.py && rm demo-project/foo.py.bak 134 | ``` 135 | 136 | And finally he records the state of files after the modification and produces 137 | a link metadata file called `update-version.[Bob's keyid].link`. 138 | ```shell 139 | # In functionary_bob directory 140 | in-toto-record stop --step-name update-version --use-dsse --signing-key bob --products demo-project/foo.py 141 | ``` 142 | 143 | Bob has done his work and can send over the sources to Carl, who will create 144 | the package for the user. 145 | 146 | ```shell 147 | # Bob has to send the update sources to Carl so that he can package them 148 | cp -r demo-project ../functionary_carl/ 149 | ``` 150 | 151 | ### Package (Carl) 152 | Now, we will perform Carl’s `package` step by executing the following commands 153 | to change to Carl's directory and create a package of the software project 154 | 155 | ```shell 156 | cd ../functionary_carl 157 | in-toto-run --step-name package --use-dsse --materials demo-project/foo.py --products demo-project.tar.gz --signing-key carl -- tar --exclude ".git" -zcvf demo-project.tar.gz demo-project 158 | ``` 159 | 160 | This will create another step link metadata file, called `package.[Carl's keyid].link`. 161 | It's time to release our software now. 162 | 163 | 164 | ### Verify final product (client) 165 | Let's first copy all relevant files into the `final_product` that is 166 | our software package `demo-project.tar.gz` and the related metadata files `root.layout`, 167 | `clone.[Bob's keyid].link`, `update-version.[Bob's keyid].link` and `package.[Carl's keyid].link`: 168 | ```shell 169 | cd .. 170 | cp owner_alice/root.layout functionary_bob/clone.210dcc50.link functionary_bob/update-version.210dcc50.link functionary_carl/package.be06db20.link functionary_carl/demo-project.tar.gz final_product/ 171 | ``` 172 | And now run verification on behalf of the client: 173 | ```shell 174 | cd final_product 175 | # Fetch Alice's public key from a trusted source to verify the layout signature 176 | # Note: The functionary public keys are fetched from the layout 177 | cp ../owner_alice/alice.pub . 178 | in-toto-verify --layout root.layout --verification-keys alice.pub 179 | ``` 180 | This command will verify that 181 | 1. the layout has not expired, 182 | 2. was signed with Alice’s private key, 183 |
and that according to the definitions in the layout 184 | 3. each step was performed and signed by the authorized functionary 185 | 4. the recorded materials and products follow the artifact rules and 186 | 5. the inspection `untar` finds what it expects. 187 | 188 | 189 | From it, you will see the meaningful output `PASSING` and a return value 190 | of `0`, that indicates verification worked out well: 191 | ```shell 192 | echo $? 193 | # should output 0 194 | ``` 195 | 196 | ### Tampering with the software supply chain 197 | Now, let’s try to tamper with the software supply chain. 198 | Imagine that someone got a hold of the source code before Carl could package it. 199 | We will simulate this by changing `demo-project/foo.py` on Carl's machine 200 | (in `functionary_carl` directory) and then let Carl package and ship the 201 | malicious code. 202 | 203 | ```shell 204 | cd ../functionary_carl 205 | echo something evil >> demo-project/foo.py 206 | ``` 207 | Carl thought that this is the genuine code he got from Bob and 208 | unwittingly packages the tampered version of foo.py 209 | 210 | ```shell 211 | in-toto-run --step-name package --use-dsse --materials demo-project/foo.py --products demo-project.tar.gz --signing-key carl -- tar --exclude ".git" -zcvf demo-project.tar.gz demo-project 212 | ``` 213 | and ships everything out as final product to the client: 214 | ```shell 215 | cd .. 216 | cp owner_alice/root.layout functionary_bob/clone.210dcc50.link functionary_bob/update-version.210dcc50.link functionary_carl/package.be06db20.link functionary_carl/demo-project.tar.gz final_product/ 217 | ``` 218 | 219 | ### Verifying the malicious product 220 | 221 | ```shell 222 | cd final_product 223 | in-toto-verify --layout root.layout --verification-keys alice.pub 224 | ``` 225 | This time, in-toto will detect that the product `foo.py` from Bob's `update-version` 226 | step was not used as material in Carl's `package` step (the verified hashes 227 | won't match) and therefore will fail verification an return a non-zero value: 228 | ```shell 229 | echo $? 230 | # should output 1 231 | ``` 232 | 233 | 234 | ### Wrapping up 235 | Congratulations! You have completed the in-toto demo! This exercise shows a very 236 | simple case in how in-toto can protect the different steps within the software 237 | supply chain. More complex software supply chains that contain more steps can be 238 | created in a similar way. You can read more about what in-toto protects against 239 | and how to use it on [in-toto's Github page](https://in-toto.github.io/). 240 | 241 | ## Cleaning up and automated run through 242 | ### Clean slate 243 | If you want to run the demo again, you can use the following script to remove all the files you created above. 244 | 245 | ```bash 246 | cd .. # You have to be the demo directory 247 | python run_demo.py -c 248 | ``` 249 | 250 | ### Tired of copy-pasting commands? 251 | The same script can be used to sequentially execute all commands listed above. Just change into the `demo` directory, run `python run_demo.py` without flags and observe the output. 252 | 253 | ```bash 254 | # In the demo directory 255 | python run_demo.py 256 | ``` 257 | -------------------------------------------------------------------------------- /final_product/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/in-toto/demo/3e2c5149e1fe926ae6deda9d64ebece15bd758f9/final_product/.keep -------------------------------------------------------------------------------- /functionary_bob/bob: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIG5QIBAAKCAYEA0Zfzonp3/FScaIP+KKuzB+OZNFpjbVGWjm3leqnFqHYLqrLc 3 | Cw5KhlXpycJqoSvZBpO+PFCksUx8U/ryklHGVoDiB84pRkvZtBoVaA4b4IHDIhz1 4 | K5NqkJgieya4fwReTxmCW0a9gH7AnDicHBCXlzMxqEdt6OKMV5g4yjKaxf8lW72O 5 | 1gSI46GSIToo+Z7UUgs3ofaM5UFIcczgCpUa5kEKocB6cSZ9U8PKRLSs0xO0ROjr 6 | cOTsfxMs8eV4bsRCWY5mAq1WM9EHDSV9WO8gqrRmanC4enNqa8jU4O3zhgJVegP9 7 | A01r9AwNt6AqgPSikwhXN/P4v1FMYV+R6N3bS1lsVWRAnwBq5RFz5zVvcY88JEkH 8 | brcBqP/A4909NXae1VMXmnoJb4EzGAkyUySBa+fHXAVJgzwyv3I48d/OIjH8NWcV 9 | mM/DQL7FtcJk3tp0YUjY5wNpcbQTnLzURtlUsd+MtGuvdlDxUUvtUYCIVKRdS8Uz 10 | YnTPjI2xzeoSHZ2ZAgMBAAECggGBAIUj0GlTAKsymFSwHCB7yXNmeejOzkAgRtJd 11 | Lxv3go7bxdd+XNdNEbw6ERPQQ2n0b52E9qBb3fKeko/KZpLaDXLf1jV9Ga0E+9sz 12 | gouiAsVfyLP/zyIKN/R4H9c5JpPRE5ONscgHrNNWMUZLk6ckRxeONqoeDcyVNO9j 13 | zBdtg/HofTPEu1pqcQagmTIwPt0qrtcbNxDUjHYJPVrE+UDfrMG9aWKM4XrFJ3Gx 14 | euigGPTQnH/1sbH6Sd0DMlbLHPDIC/N0BaJXgWId1+KkkxGEYh671yB2ZN0MN4JO 15 | q2RxSOynFY0x7yu6my8MCMbiByxnk00+scCY54r8Hs+9yECb4qlqsPYr+XbsIbG2 16 | RJGRLWLMrD/EhTyDx5fCfM/ZFHFoDy5BWO+vjUehow+PEQBsWSRWNjXgfkzPjVMa 17 | SCovCOoPY8Ghwrg4p/QG9Lf+Y0egLn6EniWPrgKnMPW3tzCvC/5sjC0y1WWciW3o 18 | RJB9nu6GShk942w9jscr4gM634vkHQKBwQDtfaWZ6ndy1Z1quD1oDiBAaeyPGpzM 19 | P/A2u2x95h/2TVo4PN+Zs1ehlNwIKoMWBwXJil4fLWmDW2081PrT/XnnInNkw4iV 20 | HWIZUcmOCQzLm0PPuxPHywbP1LDu8/IGnoYjbK8yiOBZAy/klI1A+oDHf6cmf7GY 21 | Jm4+KrDsFdaroudYOAz/twuf8KXzznZAFZMSApjq6c2ZHVju48rhWLYxMOG85XBF 22 | 4suZ3yCi337Szj1zhfIE3lvqmZbUMJDj+fMCgcEA4e201xi6U/K2H0dRFaLtUi4O 23 | EhR+VJTKzWrwne+07RVes3yrbowYs9mjg9HIKn9rYPxBG+hfmkrOeRJXYpPmfx8q 24 | kk39K1XOe787tjguD1Uj8b5PPr88t0XhxHr5XsS12xpeMR/e2lJ715pycOjnJji/ 25 | nz3ne1RHuew2j3nSnCjoDd7TmpHvyQDjRhZr8S28k2hWXfK8WuvTOj5e+SzvkMWG 26 | 8J5kLi+qFscADGXlpmFQtz57iwAI0MBjkR0yscFDAoHAEiP82EruoNjsU1CLcD1T 27 | /VeZ+DxiKb/gi225lcxUOK4j7BPKSKVIVlFWlVEZ/j6/FGv7UIpZeu0q5PCn0DWW 28 | cC9TfSjqb+l0qtZyfOT4Ez1i6qUxl5tMg+eNNFNx80t8l4wfvc5yxJnXuLAYMhRw 29 | bcy0ad5rJGIbHaiJJx9r7GRfI3/0jjvfKXJqWrs0kSSUvVVxdNAzIjT5rBW+U4RB 30 | NnSzaYhlERGH19MRXR+RQmz6iK58lB6gCsV8neyvxJo9AoHBAJBY34HOOr4IBHRX 31 | jGbWgepPoo3KqixAJJK6EKHX1TDkxmzG6oDm4aGHHAHMtqbwYhrFEJRUE0DxKpoQ 32 | LeS9ujbeIsT3LxnQ6OwHco8ptcP2EdESVm8woAo4i9aM+2ahJ8+lOSkJw8iZiqZl 33 | 91hMdeLlvwhu9MbHQkx3ryRcIUPEnv69r1TCiQFTn+HX0X92SVWlBAliXRV6Nqqv 34 | zt5E54sHqP9zM26O5Y1H96/0KpXy9y8crLJSg09cnEDK9ui7IQKBwQC5wFhP/J3z 35 | U3W+PVV4j3IvSTSZPTqg7jyHn95BZaUwT3/L6VbOssN2EavPC4rAtKwIK7gvvhLG 36 | HKp0RPXK+52ANggjopvKuR+wyHVLXdNCTroCOHTJ9vQX9GlzcLb0kjAnt7BSCUCi 37 | zvVDSCFiZR3KWsf8oMIlpf4ZcxVVqd84/ALMRQVH4ep0sBIfwrDVjRL6Cl47rLaa 38 | MnWqzjNeDVfGt45q0RSCjNWrZI6I2SvWeMLmnMhXVeUViB8XEfNH6a0= 39 | -----END RSA PRIVATE KEY----- 40 | -------------------------------------------------------------------------------- /functionary_bob/bob.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA0Zfzonp3/FScaIP+KKuz 3 | B+OZNFpjbVGWjm3leqnFqHYLqrLcCw5KhlXpycJqoSvZBpO+PFCksUx8U/ryklHG 4 | VoDiB84pRkvZtBoVaA4b4IHDIhz1K5NqkJgieya4fwReTxmCW0a9gH7AnDicHBCX 5 | lzMxqEdt6OKMV5g4yjKaxf8lW72O1gSI46GSIToo+Z7UUgs3ofaM5UFIcczgCpUa 6 | 5kEKocB6cSZ9U8PKRLSs0xO0ROjrcOTsfxMs8eV4bsRCWY5mAq1WM9EHDSV9WO8g 7 | qrRmanC4enNqa8jU4O3zhgJVegP9A01r9AwNt6AqgPSikwhXN/P4v1FMYV+R6N3b 8 | S1lsVWRAnwBq5RFz5zVvcY88JEkHbrcBqP/A4909NXae1VMXmnoJb4EzGAkyUySB 9 | a+fHXAVJgzwyv3I48d/OIjH8NWcVmM/DQL7FtcJk3tp0YUjY5wNpcbQTnLzURtlU 10 | sd+MtGuvdlDxUUvtUYCIVKRdS8UzYnTPjI2xzeoSHZ2ZAgMBAAE= 11 | -----END PUBLIC KEY----- 12 | -------------------------------------------------------------------------------- /functionary_carl/carl: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 39 | -----END RSA PRIVATE KEY----- 40 | -------------------------------------------------------------------------------- /functionary_carl/carl.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAzgLBsMFSgwBiWTBmVsyW 3 | 5KbJwLFSodAzdUhU2Bq6SdRz/W6UOBGdojZXibxupjRtAaEQW/eXDe+1CbKg6ENZ 4 | Gt2D9HGFCQZgQS8ONgNDQGiNxgApMA0T21AaUhru0vEofzdN1DfEF4CAGv5AkcgK 5 | salhTyONervFIjFEdXGelFZ7dVMV3Pp5WkZPG0jFQWjnmDZhUrtSxEtqbVghc3kK 6 | AUj9Ll/3jyi2wS92Z1j5ueN8X62hWX2xBqQ6nViOMzdujkoiYCRSwuMLRqzW2CbT 7 | L8hF1+S5KWKFzxl5sCVfpPe7V5HkgEHjwCILXTbCn2fCMKlaSbJ/MG2lW7qSY2Ro 8 | wVXWkp1wDrsJ6Ii9f2dErv9vJeOVZeO9DsooQ5EuzLCfQLEU5mn7ul7bU7rFsb8J 9 | xYOeudkNBatnNCgVMAkmDPiNA7E33bmL5ARRwU0iZicsqLQR32pmwdap8PjofxqQ 10 | k7Gtvz/iYzaLrZv33cFWWTsEOqK1gKqigSqgW9T26wO9AgMBAAE= 11 | -----END PUBLIC KEY----- 12 | -------------------------------------------------------------------------------- /owner_alice/alice: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 39 | -----END RSA PRIVATE KEY----- 40 | -------------------------------------------------------------------------------- /owner_alice/alice.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAxPX3kFs/z645x4UOC3KF 3 | Y3V80YQtKrp6YS3qU+Jlvx/XzK53lb4sCDRU9jqBBx3We45TmFUibroMd8tQXCUS 4 | e8gYCBUBqBmmz0dEHJYbW0tYF7IoapMIxhRYn76YqNdl1JoRTcmzIaOJ7QrHxQrS 5 | GpivvTm6kQ9WLeApG1GLYJ3C3Wl4bnsI1bKSv55Zi45/JawHzTzYUAIXX9qCd3Io 6 | HzDucz9IAj9Ookw0va/q9FjoPGrRB80IReVxLVnbo6pYJfu/O37jvEobHFa8ckHd 7 | YxUIg8wvkIOy1O3M74lBDm6CVI0ZO25xPlDB/4nHAE1PbA3aF3lw8JGuxLDsetxm 8 | fzgAleVt4vXLQiCrZaLf+0cM97JcT7wdHcbIvRLsij9LNP+2tWZgeZ/hIAOEdaDq 9 | cYANPDIAxfTvbe9I0sXrCtrLer1SS7GqUmdFCdkdun8erXdNF0ls9Rp4cbYhjdf3 10 | yMxdI/24LUOOQ71cHW3ITIDImm6I8KmrXFM2NewTARKfAgMBAAE= 11 | -----END PUBLIC KEY----- 12 | -------------------------------------------------------------------------------- /owner_alice/create_layout.py: -------------------------------------------------------------------------------- 1 | from cryptography.hazmat.primitives.serialization import load_pem_private_key 2 | from securesystemslib.signer import CryptoSigner 3 | from in_toto.models.layout import Layout 4 | from in_toto.models.metadata import Envelope 5 | # https://github.com/in-toto/in-toto/issues/663 6 | from in_toto.models._signer import load_public_key_from_file 7 | def main(): 8 | # Load Alice's private key to later sign the layout 9 | with open("alice", "rb") as f: 10 | key_alice = load_pem_private_key(f.read(), None) 11 | 12 | signer_alice = CryptoSigner(key_alice) 13 | # Fetch and load Bob's and Carl's public keys 14 | # to specify that they are authorized to perform certain step in the layout 15 | key_bob = load_public_key_from_file("../functionary_bob/bob.pub") 16 | key_carl = load_public_key_from_file("../functionary_carl/carl.pub") 17 | 18 | layout = Layout.read({ 19 | "_type": "layout", 20 | "keys": { 21 | key_bob["keyid"]: key_bob, 22 | key_carl["keyid"]: key_carl, 23 | }, 24 | "steps": [{ 25 | "name": "clone", 26 | "expected_materials": [], 27 | "expected_products": [["CREATE", "demo-project/foo.py"], ["DISALLOW", "*"]], 28 | "pubkeys": [key_bob["keyid"]], 29 | "expected_command": [ 30 | "git", 31 | "clone", 32 | "https://github.com/in-toto/demo-project.git" 33 | ], 34 | "threshold": 1, 35 | },{ 36 | "name": "update-version", 37 | "expected_materials": [["MATCH", "demo-project/*", "WITH", "PRODUCTS", 38 | "FROM", "clone"], ["DISALLOW", "*"]], 39 | "expected_products": [["MODIFY", "demo-project/foo.py"], ["DISALLOW", "*"]], 40 | "pubkeys": [key_bob["keyid"]], 41 | "expected_command": [], 42 | "threshold": 1, 43 | },{ 44 | "name": "package", 45 | "expected_materials": [ 46 | ["MATCH", "demo-project/*", "WITH", "PRODUCTS", "FROM", 47 | "update-version"], ["DISALLOW", "*"], 48 | ], 49 | "expected_products": [ 50 | ["CREATE", "demo-project.tar.gz"], ["DISALLOW", "*"], 51 | ], 52 | "pubkeys": [key_carl["keyid"]], 53 | "expected_command": [ 54 | "tar", 55 | "--exclude", 56 | ".git", 57 | "-zcvf", 58 | "demo-project.tar.gz", 59 | "demo-project", 60 | ], 61 | "threshold": 1, 62 | }], 63 | "inspect": [{ 64 | "name": "untar", 65 | "expected_materials": [ 66 | ["MATCH", "demo-project.tar.gz", "WITH", "PRODUCTS", "FROM", "package"], 67 | # FIXME: If the routine running inspections would gather the 68 | # materials/products to record from the rules we wouldn't have to 69 | # ALLOW other files that we aren't interested in. 70 | ["ALLOW", ".keep"], 71 | ["ALLOW", "alice.pub"], 72 | ["ALLOW", "root.layout"], 73 | ["ALLOW", "*.link"], 74 | ["DISALLOW", "*"] 75 | ], 76 | "expected_products": [ 77 | ["MATCH", "demo-project/foo.py", "WITH", "PRODUCTS", "FROM", "update-version"], 78 | # FIXME: See expected_materials above 79 | ["ALLOW", "demo-project/.git/*"], 80 | ["ALLOW", "demo-project.tar.gz"], 81 | ["ALLOW", ".keep"], 82 | ["ALLOW", "alice.pub"], 83 | ["ALLOW", "root.layout"], 84 | ["ALLOW", "*.link"], 85 | ["DISALLOW", "*"] 86 | ], 87 | "run": [ 88 | "tar", 89 | "xzf", 90 | "demo-project.tar.gz", 91 | ] 92 | }], 93 | }) 94 | 95 | metadata = Envelope.from_signable(layout) 96 | 97 | # Sign and dump layout to "root.layout" 98 | metadata.create_signature(signer_alice) 99 | metadata.dump("root.layout") 100 | print('Created demo in-toto layout as "root.layout".') 101 | 102 | if __name__ == '__main__': 103 | main() 104 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | in-toto==3.0.0 2 | cryptography==45.0.3 3 | -------------------------------------------------------------------------------- /run_demo.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import shlex 4 | import subprocess 5 | import argparse 6 | import time 7 | from shutil import copyfile, copytree, rmtree 8 | 9 | NO_PROMPT = False 10 | 11 | def prompt_key(prompt): 12 | if NO_PROMPT: 13 | print("\n" + prompt) 14 | return 15 | inp = False 16 | while inp != "": 17 | try: 18 | inp = input("\n{} -- press any key to continue".format(prompt)) 19 | except Exception: 20 | pass 21 | 22 | def supply_chain(): 23 | 24 | prompt_key("Define supply chain layout (Alice)") 25 | os.chdir("owner_alice") 26 | create_layout_cmd = "python create_layout.py" 27 | print(create_layout_cmd) 28 | subprocess.call(shlex.split(create_layout_cmd)) 29 | 30 | prompt_key("Clone source code (Bob)") 31 | os.chdir("../functionary_bob") 32 | clone_cmd = ("in-toto-run" 33 | " --verbose" 34 | " --use-dsse" 35 | " --step-name clone --products demo-project/foo.py" 36 | " --signing-key bob -- git clone https://github.com/in-toto/demo-project.git") 37 | print(clone_cmd) 38 | subprocess.call(shlex.split(clone_cmd)) 39 | 40 | prompt_key("Update version number (Bob)") 41 | update_version_start_cmd = ("in-toto-record" 42 | " start" 43 | " --verbose" 44 | " --use-dsse" 45 | " --step-name update-version" 46 | " --signing-key bob" 47 | " --materials demo-project/foo.py") 48 | 49 | print(update_version_start_cmd) 50 | subprocess.call(shlex.split(update_version_start_cmd)) 51 | 52 | update_version = "echo 'VERSION = \"foo-v1\"\n\nprint(\"Hello in-toto\")\n' > demo-project/foo.py" 53 | print(update_version) 54 | subprocess.call(update_version, shell=True) 55 | 56 | update_version_stop_cmd = ("in-toto-record" 57 | " stop" 58 | " --verbose" 59 | " --use-dsse" 60 | " --step-name update-version" 61 | " --signing-key bob" 62 | " --products demo-project/foo.py") 63 | 64 | print(update_version_stop_cmd) 65 | subprocess.call(shlex.split(update_version_stop_cmd)) 66 | 67 | copytree("demo-project", "../functionary_carl/demo-project") 68 | 69 | prompt_key("Package (Carl)") 70 | os.chdir("../functionary_carl") 71 | package_cmd = ("in-toto-run" 72 | " --verbose" 73 | " --use-dsse" 74 | " --step-name package --materials demo-project/foo.py" 75 | " --products demo-project.tar.gz" 76 | " --signing-key carl --record-streams" 77 | " -- tar --exclude '.git' -zcvf demo-project.tar.gz demo-project") 78 | print(package_cmd) 79 | subprocess.call(shlex.split(package_cmd)) 80 | 81 | 82 | prompt_key("Create final product") 83 | os.chdir("..") 84 | copyfile("owner_alice/root.layout", "final_product/root.layout") 85 | copyfile("functionary_bob/clone.210dcc50.link", "final_product/clone.210dcc50.link") 86 | copyfile("functionary_bob/update-version.210dcc50.link", "final_product/update-version.210dcc50.link") 87 | copyfile("functionary_carl/package.be06db20.link", "final_product/package.be06db20.link") 88 | copyfile("functionary_carl/demo-project.tar.gz", "final_product/demo-project.tar.gz") 89 | 90 | 91 | prompt_key("Verify final product (client)") 92 | os.chdir("final_product") 93 | copyfile("../owner_alice/alice.pub", "alice.pub") 94 | verify_cmd = ("in-toto-verify" 95 | " --verbose" 96 | " --layout root.layout" 97 | " --verification-keys alice.pub") 98 | print(verify_cmd) 99 | retval = subprocess.call(shlex.split(verify_cmd)) 100 | print("Return value: " + str(retval)) 101 | 102 | 103 | 104 | 105 | prompt_key("Tampering with the supply chain") 106 | os.chdir("../functionary_carl") 107 | tamper_cmd = "echo 'something evil' >> demo-project/foo.py" 108 | print(tamper_cmd) 109 | subprocess.call(tamper_cmd, shell=True) 110 | 111 | 112 | prompt_key("Package (Carl)") 113 | package_cmd = ("in-toto-run" 114 | " --verbose" 115 | " --use-dsse" 116 | " --step-name package --materials demo-project/foo.py" 117 | " --products demo-project.tar.gz" 118 | " --signing-key carl --record-streams" 119 | " -- tar --exclude '.git' -zcvf demo-project.tar.gz demo-project") 120 | print(package_cmd) 121 | subprocess.call(shlex.split(package_cmd)) 122 | 123 | 124 | prompt_key("Create final product") 125 | os.chdir("..") 126 | copyfile("owner_alice/root.layout", "final_product/root.layout") 127 | copyfile("functionary_bob/clone.210dcc50.link", "final_product/clone.210dcc50.link") 128 | copyfile("functionary_bob/update-version.210dcc50.link", "final_product/update-version.210dcc50.link") 129 | copyfile("functionary_carl/package.be06db20.link", "final_product/package.be06db20.link") 130 | copyfile("functionary_carl/demo-project.tar.gz", "final_product/demo-project.tar.gz") 131 | 132 | 133 | prompt_key("Verify final product (client)") 134 | os.chdir("final_product") 135 | copyfile("../owner_alice/alice.pub", "alice.pub") 136 | verify_cmd = ("in-toto-verify" 137 | " --verbose" 138 | " --layout root.layout" 139 | " --verification-keys alice.pub") 140 | 141 | print(verify_cmd) 142 | retval = subprocess.call(shlex.split(verify_cmd)) 143 | print("Return value: " + str(retval)) 144 | 145 | 146 | def main(): 147 | parser = argparse.ArgumentParser() 148 | parser.add_argument("-n", "--no-prompt", help="No prompt.", 149 | action="store_true") 150 | parser.add_argument("-c", "--clean", help="Remove files created during demo.", 151 | action="store_true") 152 | args = parser.parse_args() 153 | 154 | if args.clean: 155 | files_to_delete = [ 156 | "owner_alice/root.layout", 157 | "functionary_bob/clone.210dcc50.link", 158 | "functionary_bob/update-version.210dcc50.link", 159 | "functionary_bob/demo-project", 160 | "functionary_carl/package.be06db20.link", 161 | "functionary_carl/demo-project.tar.gz", 162 | "functionary_carl/demo-project", 163 | "final_product/alice.pub", 164 | "final_product/demo-project.tar.gz", 165 | "final_product/package.be06db20.link", 166 | "final_product/clone.210dcc50.link", 167 | "final_product/update-version.210dcc50.link", 168 | "final_product/untar.link", 169 | "final_product/root.layout", 170 | "final_product/demo-project", 171 | ] 172 | 173 | for path in files_to_delete: 174 | if os.path.isfile(path): 175 | os.remove(path) 176 | elif os.path.isdir(path): 177 | rmtree(path) 178 | 179 | sys.exit(0) 180 | if args.no_prompt: 181 | global NO_PROMPT 182 | NO_PROMPT = True 183 | 184 | 185 | supply_chain() 186 | 187 | if __name__ == '__main__': 188 | main() 189 | -------------------------------------------------------------------------------- /run_demo_md.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | run_demo_md.py 4 | 5 | 6 | Lukas Puehringer 7 | 8 | 9 | Jul 17, 2019 10 | 11 | 12 | Provides a script that extracts the demo code snippets from README.md and 13 | runs them in a shell, raising `SystemExit`, if the output is not as expected. 14 | 15 | virtualenv setup and installation of in-toto, as described in the demo 16 | instructions, is not performed by this script and must be done before running 17 | it. Snippets are run in a temporary directory, which is removed afterwards. 18 | 19 | NOTE: Currently, the script runs all snippets marked as `shell` snippets (see 20 | `SNIPPET_PATTERN`). To exclude a snippet from execution it must be marked as 21 | something else (e.g. `bash` to get the same syntax highlighting). 22 | 23 | """ 24 | import os 25 | import re 26 | import shutil 27 | import sys 28 | import tempfile 29 | import difflib 30 | import subprocess 31 | 32 | # The file pointed to by `INSTRUCTIONS_FN` contains `shell` code snippets that 33 | # may be extracted using the regex defined in `SNIPPET_PATTERN`, and executed 34 | # to generate a combined stdout/stderr equal to `EXPECTED_STDOUT`. 35 | INSTRUCTIONS_FN = "README.md" 36 | SNIPPET_PATTERN = r"```shell\n([\s\S]*?)\n```" 37 | 38 | EXPECTED_STDOUT = \ 39 | """+ cd owner_alice 40 | + python create_layout.py 41 | Created demo in-toto layout as "root.layout". 42 | + cd ../functionary_bob 43 | + in-toto-run --step-name clone --use-dsse --products demo-project/foo.py --signing-key bob -- git clone https://github.com/in-toto/demo-project.git 44 | + in-toto-record start --step-name update-version --use-dsse --signing-key bob --materials demo-project/foo.py 45 | + sed -i.bak s/v0/v1/ demo-project/foo.py 46 | + rm demo-project/foo.py.bak 47 | + in-toto-record stop --step-name update-version --use-dsse --signing-key bob --products demo-project/foo.py 48 | + cp -r demo-project ../functionary_carl/ 49 | + cd ../functionary_carl 50 | + in-toto-run --step-name package --use-dsse --materials demo-project/foo.py --products demo-project.tar.gz --signing-key carl -- tar --exclude .git -zcvf demo-project.tar.gz demo-project 51 | + cd .. 52 | + cp owner_alice/root.layout functionary_bob/clone.210dcc50.link functionary_bob/update-version.210dcc50.link functionary_carl/package.be06db20.link functionary_carl/demo-project.tar.gz final_product/ 53 | + cd final_product 54 | + cp ../owner_alice/alice.pub . 55 | + in-toto-verify --layout root.layout --verification-keys alice.pub 56 | + echo 0 57 | 0 58 | + cd ../functionary_carl 59 | + echo something evil 60 | + in-toto-run --step-name package --use-dsse --materials demo-project/foo.py --products demo-project.tar.gz --signing-key carl -- tar --exclude .git -zcvf demo-project.tar.gz demo-project 61 | + cd .. 62 | + cp owner_alice/root.layout functionary_bob/clone.210dcc50.link functionary_bob/update-version.210dcc50.link functionary_carl/package.be06db20.link functionary_carl/demo-project.tar.gz final_product/ 63 | + cd final_product 64 | + in-toto-verify --layout root.layout --verification-keys alice.pub 65 | (in-toto-verify) RuleVerificationError: 'DISALLOW *' matched the following artifacts: ['demo-project/foo.py'] 66 | Full trace for 'expected_materials' of item 'package': 67 | Available materials (used for queue): 68 | ['demo-project/foo.py'] 69 | Available products: 70 | ['demo-project.tar.gz'] 71 | Queue after 'MATCH demo-project/* WITH PRODUCTS FROM update-version': 72 | ['demo-project/foo.py'] 73 | 74 | + echo 1 75 | 1 76 | """ 77 | 78 | # Setup a test directory with all necessary demo files and change into it. This 79 | # lets us easily clean up all the files created during the demo eventually. 80 | demo_dir = os.path.dirname(os.path.realpath(__file__)) 81 | tmp_dir = os.path.realpath(tempfile.mkdtemp()) 82 | test_dir = os.path.join(tmp_dir, os.path.basename(demo_dir)) 83 | shutil.copytree(demo_dir, test_dir) 84 | os.chdir(test_dir) 85 | 86 | # Wrap test code in try/finally to always tear down test directory and files 87 | try: 88 | # Extract all shell code snippets from demo instructions 89 | with open(INSTRUCTIONS_FN) as fp: 90 | readme = fp.read() 91 | snippets = re.findall(SNIPPET_PATTERN, readme) 92 | 93 | # Create script from all snippets, with shell xtrace mode (set -x) for 94 | # detailed output and make sure that it has the expected prefix (PS4='+ ') 95 | script = "PS4='+ '\nset -x\n{}".format("\n".join(snippets)) 96 | 97 | # Execute script in one shell so we can run commands like `cd` 98 | # NOTE: Would be nice to use `in_toto.process.run_duplicate_streams` to show 99 | # output in real time, but the method does not support the required kwargs. 100 | proc = subprocess.Popen( 101 | ["/bin/sh", "-c", script], 102 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT, 103 | universal_newlines=True) 104 | stdout, _ = proc.communicate() 105 | 106 | print(stdout) 107 | 108 | # Fail if the output is not what we expected 109 | if stdout != EXPECTED_STDOUT: 110 | difflist = list(difflib.Differ().compare( 111 | EXPECTED_STDOUT.splitlines(), 112 | stdout.splitlines())) 113 | raise SystemExit( 114 | "#### DIFFERENCE:\n\n{}\n\nDemo test failed due to unexpected output " 115 | "(see above). :(".format("\n".join(difflist))) 116 | 117 | print("{}\nDemo test ran as expected. :)".format(stdout)) 118 | 119 | finally: 120 | # Change back to where we were in the beginning and tear down test directory 121 | os.chdir(demo_dir) 122 | shutil.rmtree(test_dir) 123 | --------------------------------------------------------------------------------