feat: initial commit

Signed-off-by: Harald Hoyer <harald@matterlabs.dev>
This commit is contained in:
Harald Hoyer 2023-10-24 09:38:58 +02:00 committed by Harald Hoyer
commit c2411a45a7
Signed by: harald
GPG key ID: F519A1143B3FBE32
29 changed files with 6542 additions and 0 deletions

4
.github/CODEOWNERS vendored Normal file
View file

@ -0,0 +1,4 @@
# Each line is a file pattern followed by one or more owners.
# Owners will be automatically notified about new PRs and
# an owner's approval is required to merge to protected branches.
* @haraldh @thomasknauth

41
.github/workflows/container.yml vendored Normal file
View file

@ -0,0 +1,41 @@
name: Container
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
packages: write
contents: read
jobs:
push_to_registry:
name: Build and push containers image to GitHub Packages
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up env
run: echo "repository_owner=${GITHUB_REPOSITORY_OWNER,,}" >>${GITHUB_ENV}
- name: Build and Push Container
uses: docker/build-push-action@v5
with:
tags: |
ghcr.io/${{env.repository_owner}}/${{ github.event.repository.name }}:latest
matterlabsrobot/${{ github.event.repository.name }}:latest
push: ${{ github.event_name == 'push' || github.event_name == 'schedule' }}

40
.github/workflows/go.yml vendored Normal file
View file

@ -0,0 +1,40 @@
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
name: Go
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Prep
run: |
wget -qO - https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key | sudo apt-key add -
sudo bash -c 'echo "deb [arch=amd64] https://download.01.org/intel-sgx/sgx_repo/ubuntu focal main" > /etc/apt/sources.list.d/intel-sgx.list'
sudo apt -o Acquire::Retries=3 update
sudo apt -o Acquire::Retries=3 install -y --no-install-recommends \
libsgx-headers \
libsgx-enclave-common \
libsgx-urts \
libsgx-dcap-quote-verify \
libsgx-dcap-quote-verify-dev
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.19
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...

72
.github/workflows/nix.yml vendored Normal file
View file

@ -0,0 +1,72 @@
name: nix
on:
pull_request:
branches: [ "main" ]
paths:
- '**.nix'
- 'go.mod'
- 'go.sum'
- 'flake.lock'
push:
branches: [ "main" ]
paths:
- '**.nix'
- 'go.mod'
- 'go.sum'
- 'flake.lock'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@6a9a9e84a173d90b3ffb42c5ddaf9ea033fad011 # v23
with:
extra_nix_config: |
access-tokens = github.com=${{ github.token }}
- run: nix flake check -L --show-trace --keep-going
fmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@6a9a9e84a173d90b3ffb42c5ddaf9ea033fad011 # v23
with:
extra_nix_config: |
access-tokens = github.com=${{ github.token }}
- run: nix fmt
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@6a9a9e84a173d90b3ffb42c5ddaf9ea033fad011 # v23
with:
extra_nix_config: |
access-tokens = github.com=${{ github.token }}
- uses: cachix/cachix-action@v12
continue-on-error: true
with:
name: haraldh
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- run: nix build -L .
develop:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@6a9a9e84a173d90b3ffb42c5ddaf9ea033fad011 # v23
with:
extra_nix_config: |
access-tokens = github.com=${{ github.token }}
- uses: cachix/cachix-action@v12
continue-on-error: true
with:
name: haraldh
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- run: nix develop -L -c go test ./...

18
.github/workflows/secrets_scanner.yaml vendored Normal file
View file

@ -0,0 +1,18 @@
name: Leaked Secrets Scan
on: [pull_request]
jobs:
TruffleHog:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
fetch-depth: 0
- name: TruffleHog OSS
uses: trufflesecurity/trufflehog@6914dacde37c95874645cc208ce63a58c888cc6c # v3.60.4
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
extra_args: --debug --only-verified

24
.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
# IDE
/.idea

41
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,41 @@
# Contribution Guidelines
Hello! Thanks for your interest in joining the mission to accelerate the mass adoption of crypto for personal
sovereignty! We welcome contributions from anyone on the internet, and are grateful for even the smallest of fixes!
## Ways to contribute
There are many ways to contribute to the project:
1. Open issues: if you find a bug, have something you believe needs to be fixed, or have an idea for a feature, please
open an issue.
2. Add color to existing issues: provide screenshots, code snippets, and whatever you think would be helpful to resolve
issues.
3. Resolve issues: either by showing an issue isn't a problem and the current state is ok as is or by fixing the problem
and opening a PR.
4. Report security issues, see [our security policy](SECURITY.md).
5. [Join the team!](https://matterlabs.notion.site/Shape-the-future-of-Ethereum-at-Matter-Labs-dfb3b5a037044bb3a8006af2eb0575e0)
## Fixing issues
To contribute code fixing issues, please fork the repo, fix an issue, commit, add documentation as per the PR template,
and the repo's maintainers will review the PR.
[here](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork)
for guidance how to work with PRs created from a fork.
## Licenses
If you contribute to this project, your contributions will be made to the project under the Mozilla Public License 2.0
license.
## Resources
We aim to make it as easy as possible to contribute to the mission. This is still WIP, and we're happy for contributions
and suggestions here too.
## Code of Conduct
Be polite and respectful.
### Thank you

53
Dockerfile Normal file
View file

@ -0,0 +1,53 @@
FROM docker.io/ubuntu:20.04 AS pluginbuilder
ARG VERSION=1.20.4
ARG CGO_ENABLED=1
ARG BUILD_TAGS="default"
ENV JOBS=2
RUN set -eux; \
DEBIAN_FRONTEND=noninteractive apt-get update -y; \
DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl; \
:
RUN set -eux; \
curl -fsSLo /usr/share/keyrings/intel.asc https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key; \
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/intel.asc] https://download.01.org/intel-sgx/sgx_repo/ubuntu focal main" > /etc/apt/sources.list.d/intel-sgx.list; \
DEBIAN_FRONTEND=noninteractive apt-get update; \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential \
cmake \
rsync \
pkg-config \
libssl-dev \
libcurl4-openssl-dev \
libprotobuf-dev \
protobuf-compiler \
clang \
libsgx-headers \
libsgx-dcap-quote-verify-dev \
; \
:
RUN mkdir /goroot && mkdir /go
RUN curl https://storage.googleapis.com/golang/go${VERSION}.linux-amd64.tar.gz \
| tar xvzf - -C /goroot --strip-components=1
ENV GOPATH /go
ENV GOROOT /goroot
ENV PATH $GOROOT/bin:$GOPATH/bin:$PATH
WORKDIR /
RUN --mount=type=cache,target=/root/.cache --mount=type=cache,target=/go --mount=type=bind,target=/data \
set -eux; \
mkdir -p /go/src/github.com/matter-labs/vault-auth-tee; \
cd /go/src/github.com/matter-labs/vault-auth-tee; \
rsync -a --delete-after /data/ ./ ; \
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o vault-auth-tee; \
mkdir -p /opt/vault/plugins; \
cp vault-auth-tee /opt/vault/plugins/vault-auth-tee; \
:
FROM scratch
WORKDIR /opt/vault/plugins
COPY --from=pluginbuilder /opt/vault/plugins/vault-auth-tee /opt/vault/plugins/vault-auth-tee

366
LICENSE Normal file
View file

@ -0,0 +1,366 @@
Copyright (c) 2015 HashiCorp, Inc.
Copyright (c) 2023 Matter Labs
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.

96
README.md Normal file
View file

@ -0,0 +1,96 @@
# vault-auth-tee
TEE remote attestation plugin for Hashicorp Vault
# ⚠️☢️☣️ WARNING: not yet for production use ☣️☢️⚠️
## License
All of the code is licensed under the Mozilla Public License 2.0 unless otherwise specified.
Most of the vault plugin code is based on the vault `builtin/credential/cert` plugin.
## Build Setup
```bash
$ wget -qO - https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key | sudo apt-key add -
$ sudo bash -c 'echo "deb [arch=amd64] https://download.01.org/intel-sgx/sgx_repo/ubuntu focal main" > /etc/apt/sources.list.d/intel-sgx.list'
$ sudo apt update
$ sudo apt install -y --no-install-recommends \
libsgx-headers \
libsgx-enclave-common \
libsgx-urts \
libsgx-dcap-quote-verify \
libsgx-dcap-quote-verify-dev
```
## Configuration
`Create` or `Update` via the `${plugin}/tees/$name` endpoint
```json
{
"name": "TEE_role_name",
"token_policies": "policy1,policy2,...",
"types": "sgx",
"sgx_mrsigner": "298037d88782e022e019b3020745b78aa40ed95c77da4bf7f3253d3a44c4fd7e",
"sgx_mrenclave": "18946b3547d3ca036f4df7b516857e28fd512d69fed3411dc660537912faabf8",
"sgx_isv_prodid": 0,
"sgx_min_isv_svn": 0,
"sgx_allowed_tcb_levels": "Ok,ConfigNeeded,OutOfDate,OutOfDateConfigNeeded,SwHardeningNeeded,ConfigAndSwHardeningNeeded"
}
```
* At least one of `sgx_mrsigner` or `sgx_mrenclave` must be set. If both are set, both are used for matching.
* `sgx_isv_prodid` is optional and defaults to `0`.
* `sgx_min_isv_svn` is optional and defaults to `0`.
* `sgx_allowed_tcb_levels` is optional and defaults to `Ok`.
## Authentication
- Client TEE generates a self-signed TLS client certificate
- Client TEE generates an attestation report, which includes the hash of the public key of the client certificate (in case of SGX, a sha256 sum of the public key)
- Client TEE fetches all collateral material via e.g. Intel DCAP ([`tee_qv_get_collateral`](https://github.com/intel/SGXDataCenterAttestationPrimitives/blob/4cb5c8b81f126f9aa3ee921d7980a909a9bd676d/QuoteVerification/dcap_quoteverify/inc/sgx_dcap_quoteverify.h#L234-L238))
- Client TEE sends POST request with a TLS connection using the client certificate
to Vault via the `${plugin}/login` endpoint with the name, attestation report and the attestation collateral material
- An optional challenge can be included in the POST request, which is then included in the attestation report of the vault response
```json
{
"name": "The name of the TEE role to authenticate against.",
"quote": "The quote Base64 encoded.",
"collateral": "The collateral Json string encoded.",
"challenge": "An optional challenge hex encoded."
}
```
The response contains the Vault token and, if a challenge was included,
the vault attestation report, which must contain the challenge bytes in the report_data of the quote.
```json
{
"auth": {
"client_token": "The Vault token.",
"....": "...."
},
"data": {
"quote": "The vault quote Base64 encoded.",
"collateral": "The vault collateral Json string encoded."
}
}
```
### Collateral Json encoding
See [sgx_ql_lib_common.h](https://github.com/intel/SGXDataCenterAttestationPrimitives/blob/4cb5c8b81f126f9aa3ee921d7980a909a9bd676d/QuoteGeneration/quote_wrapper/common/inc/sgx_ql_lib_common.h#L202-L227)
```json
{
"major_version": uint16,
"minor_version": uint16,
"tee_type": uint32,
"pck_crl_issuer_chain": []byte,
"root_ca_crl": []byte,
"pck_crl": []byte,
"tcb_info_issuer_chain": []byte,
"tcb_info": []byte,
"qe_identity_issuer_chain": []byte,
"qe_identity": []byte
}
```

70
SECURITY.md Normal file
View file

@ -0,0 +1,70 @@
# Security Policy
We truly appreciate efforts to discover and disclose security issues responsibly!
## Vulnerabilities
We take an impact-first approach instead of a rules-first approach. Therefore, if you believe you found the impactful
issue, please email us at
[security@matterlabs.dev](mailto:security@matterlabs.dev).
### PGP Key
The following PGP key may be used to communicate sensitive information to developers:
Fingerprint: `5FED B2D0 EA2C 4906 DD66 71D7 A2C5 0B40 CE3C F297`
```
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGEBmQkBEAD6tlkBEZFMvR8kOgxXX857nC2+oTik6TopJz4uCskuqDaeldMy
l+26BBzLkIeO1loS+bzVgnNFJRrGt9gv98MzNEHJVv6D7GsSLlUX/pz7Lxn0J4ry
o5XIk3MQTCUBdaXGs6GBLl5Xe8o+zNj4MKd4zjgDLinITNlE/YZCDsXyvYS3YFTQ
cwaUTNlawkKgw4BLaEqwB2JuyEhI9wx5X7ibjFL32sWMolYsNAlzFQzM09HCurTn
q0DYau9kPJARcEk9/DK2iq0z3gMCQ8iRTDaOWd8IbSP3HxcEoM5j5ZVAlULmjmUE
StDaMPLj0Kh01Tesh/j+vjchPXHT0n4zqi1+KOesAOk7SIwLadHfQMTpkU7G2fR1
BrA5MtlzY+4Rm6o7qu3dpZ+Nc4iM3FUnaQRpvn4g5nTh8vjG94OCzX8DXWrCKyxx
amCs9PLDYOpx84fXYv4frkWpKh2digDSUGKhoHaOSnqyyvu3BNWXBCQZJ20rqEIu
sXOQMxWIoWCOOPRRvrHrKDA2hpoKjs3pGsProfpVRzb9702jhWpTfbDp9WjQlFtX
2ZIDxlwAxcugClgrp5JiUxvhg2A9lDNwCF7r1e68uNv5usBZQVKPJmnvS2nWgKy8
x9oJsnwrEjxwiRHd34UvfMkwY9RENSJ+NoXqBdS7Lwz4m6vgbzq6K56WPQARAQAB
tCRaa1N5bmMgU2VjdXJpdHkgPHNlY3VyaXR5QHprc3luYy5pbz6JAk4EEwEKADgW
IQRf7bLQ6ixJBt1mcdeixQtAzjzylwUCYQGZCQIbAwULCQgHAgYVCgkICwIEFgID
AQIeAQIXgAAKCRCixQtAzjzyl5y8EAC/T3oq88Dak2b+5TlWdU2Gpm6924eAqlMt
y1KksDezzNQUlPiCUVllpin2PIjU/S+yzMWKXJA04LoVkEPfPOWjAaavLOjRumxu
MR6P2dVUg1InqzYVsJuRhKSpeexzNA5qO2BPM7/I2Iea1IoJPjogGbfXCo0r5kne
KU7a5GEa9eDHxpHTsbphQe2vpQ1239mUJrFpzAvILn6jV1tawMn5pNCXbsa8l6l2
gtlyQPdOQECy77ZJxrgzaUBcs/RPzUGhwA/qNuvpF0whaCvZuUFMVuCTEu5LZka2
I9Rixy+3jqBeONBgb+Fiz5phbiMX33M9JQwGONFaxdvpFTerLwPK2N1T8zcufa01
ypzkWGheScFZemBxUwXwK4x579wjsnfrY11w0p1jtDgPTnLlXUA2mom4+7MyXPg0
F75qh6vU1pdXaCVkruFgPVtIw+ccw2AxD50iZQ943ZERom9k165dR9+QxOVMXQ4P
VUxsFZWvK70/s8TLjsGljvSdSOa85iEUqSqh0AlCwIAxLMiDwh5s/ZgiHoIM6Xih
oCpuZyK9p0dn+DF/XkgAZ/S91PesMye3cGm6M5r0tS26aoc2Pk6X37Hha1pRALwo
MOHyaGjc/jjcXXxv6o55ALrOrzS0LQmLZ+EHuteCT15kmeY3kqYJ3og62KgiDvew
dKHENvg7d7kCDQRhAZleARAA6uD6WfdqGeKV5i170+kLsxR3QGav0qGNAbxpSJyn
iHQ8u7mQk3S+ziwN2AAopfBk1je+vCWtEGC3+DWRRfJSjLbtaBG8e6kLP3/cGA75
qURz6glTG4nl5fcEAa6B1st0OxjVWiSLX3g/yjz8lznQb9awuRjdeHMnyx5DsJUN
d+Iu5KxGupQvKGOMKivSvC8VWk9taaQRpRF+++6stLCDk3ZtlxiopMs3X2jAp6xG
sOBbix1cv9BTsfaiL7XDL/gviqBPXYY5L42x6+jnPo5lROfnlLYkWrv6KZr7HD4k
tRXeaSwxLD2EkUyb16Jpp0be/ofvBtITGUDDLCGBiaXtx/v8d52MARjsyLJSYloj
1yiW01LfAiWHUC4z5jl2T7E7sicrlLH1M8Z6WbuqjdeaYwtfyPA2YCKr/3fn6pIo
D+pYaBSESmhA92P+XVaf5y2BZ6Qf8LveDpWwsVGdBGh9T0raA1ooe1GESLjmIjUa
z5AeQ/uXL5Md9I6bpMUUJYQiH19RPcFlJriI3phXyyf6Wlkk8oVEeCWyzcmw+x1V
deRTvE2x4WIwKGLXRNjin2j1AP7vU2HaNwlPrLijqdyi68+0irRQONoH7Qonr4ca
xWgL+pAaa3dWxf0xqK7uZFp4aTVWlr2uXtV/eaUtLmGMCU0jnjb109wg5L0F7WRT
PfEAEQEAAYkCNgQYAQoAIBYhBF/tstDqLEkG3WZx16LFC0DOPPKXBQJhAZleAhsM
AAoJEKLFC0DOPPKXAAEP/jK7ch9GkoaYlsuqY/aHtxEwVddUDOxjyn3FMDoln85L
/n8AmLQb2bcpKSqpaJwMbmfEyr5MDm8xnsBTfx3u6kgaLOWfKxjLQ6PM7kgIMdi4
bfaRRuSEI1/R6c/hNpiGnzAeeexldH1we+eH1IVmh4crdat49S2xh7Qlv9ahvgsP
LfKl3rJ+aaX/Ok0AHzhvSfhFpPr1gAaGeaRt+rhlZsx2QyG4Ez8p2nDAcAzPiB3T
73ENoBIX6mTPfPm1UgrRyFKBqtUzAodz66j3r6ebBlWzIRg8iZenVMAxzjINAsxN
w1Bzfgsi5ZespfsSlmEaa7jJkqqDuEcLa2YuiFAue7Euqwz1aGeq1GfTicQioSCb
Ur/LGyz2Mj3ykbaP8p5mFVcUN51yQy6OcpvR/W1DfRT9SHFT/bCf9ixsjB2HlZGo
uxPJowwqmMgHd755ZzPDUM9YDgLI1yXdcYshObv3Wq537JAxnZJCGRK4Y8SwrMSh
8WRxlaM0AGWXiJFIDD4bQPIdnF3X8w0cGWE5Otkb8mMHOT+rFTVlDODwm1zF6oIG
PTwfVrpiZBwiUtfJol1exr/MzSPyGoJnYs3cRf2E3O+D1LbcR8w0LbjGuUy38Piz
ZO/vCeyJ3JZC5kE8nD+XBA4idwzh0BKEfH9t+WchQ3Up9rxyzLyQamoqt5Xby4pY
=xkM3
-----END PGP PUBLIC KEY BLOCK-----
```

65
flake.lock generated Normal file
View file

@ -0,0 +1,65 @@
{
"nodes": {
"gitignore": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1694102001,
"narHash": "sha256-vky6VPK1n1od6vXbqzOXnekrQpTL4hbPAwUhT5J9c9E=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "9e21c80adf67ebcb077d75bd5e7d724d21eeafd6",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"mynixpkgs": {
"locked": {
"lastModified": 1694694534,
"narHash": "sha256-8xmRrzjyT3ZWt+sCegaKRx+PIkB1W69G+CBz3Atf35A=",
"owner": "haraldh",
"repo": "nixpkgs",
"rev": "15d456e58b39f691d4815101723d2a767853f8da",
"type": "github"
},
"original": {
"owner": "haraldh",
"ref": "intel-dcap-openssl",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1694499547,
"narHash": "sha256-R7xMz1Iia6JthWRHDn36s/E248WB1/je62ovC/dUVKI=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "e5f018cf150e29aac26c61dac0790ea023c46b24",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-23.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"gitignore": "gitignore",
"mynixpkgs": "mynixpkgs",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

52
flake.nix Normal file
View file

@ -0,0 +1,52 @@
{
description = "vault auth plugin for remote attestation of TEEs";
inputs = {
# for libsgx-dcap-quote-verify
nixpkgs.url = "github:nixos/nixpkgs/nixos-23.05";
mynixpkgs.url =
"github:haraldh/nixpkgs/intel-dcap-openssl";
gitignore = {
url = "github:hercules-ci/gitignore.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, gitignore, mynixpkgs, ... }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
mypkgs = import mynixpkgs { inherit system; };
bin = pkgs.buildGoModule {
buildInputs = with mypkgs; [ sgx-sdk libsgx-dcap-quote-verify ];
CGO_CFLAGS =
"-I${mypkgs.libsgx-dcap-quote-verify.dev}/include -I${mypkgs.sgx-sdk}/include";
LDFLAGS = "-L${mypkgs.libsgx-dcap-quote-verify.dev}/lib";
name = "vault-auth-tee";
src = gitignore.lib.gitignoreSource ./.;
vendorSha256 = "sha256-9l1EVnWIJ+FdIcEic14M/B2BLD/Ffj+dCkompa06KJQ=";
};
dockerImage = pkgs.dockerTools.buildImage {
name = "vault-auth-tee";
tag = "latest";
copyToRoot = [
bin
# pkgs.vault
];
#config = { Cmd = [ "${bin}/bin/vault" ]; };
};
in
with pkgs; {
formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixpkgs-fmt;
packages.x86_64-linux = {
inherit bin dockerImage;
default = bin;
};
devShells.x86_64-linux.default = mkShell {
inputsFrom = [ bin ];
buildInputs = with pkgs; [ dive go_1_19 gotools mypkgs.sgx-sdk mypkgs.libsgx-dcap-quote-verify ];
};
};
}

229
go.mod Normal file
View file

@ -0,0 +1,229 @@
module github.com/matter-labs/vault-auth-tee
go 1.19
require (
github.com/hashicorp/go-hclog v1.5.0
github.com/hashicorp/go-rootcerts v1.0.2
github.com/hashicorp/vault v1.14.1
github.com/hashicorp/vault/api v1.9.2
github.com/hashicorp/vault/sdk v0.9.2-0.20230704151349-7522ca248f90
github.com/stretchr/testify v1.8.4
golang.org/x/crypto v0.14.0
)
require (
cloud.google.com/go/compute v1.19.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.0.1 // indirect
cloud.google.com/go/kms v1.10.2 // indirect
cloud.google.com/go/monitoring v1.13.0 // indirect
github.com/Azure/azure-sdk-for-go v67.2.0+incompatible // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/DataDog/datadog-go v3.2.0+incompatible // indirect
github.com/Jeffail/gabs v1.1.1 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230626094100-7e9e0395ebec // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.62.301 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
github.com/aws/aws-sdk-go v1.44.268 // indirect
github.com/axiomhq/hyperloglog v0.0.0-20220105174342-98591331716a // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/boombuler/barcode v1.0.1 // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible // indirect
github.com/circonus-labs/circonusllhist v0.1.3 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba // indirect
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc // indirect
github.com/digitalocean/godo v1.7.5 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 // indirect
github.com/emicklei/go-restful/v3 v3.10.1 // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/analysis v0.20.0 // indirect
github.com/go-openapi/errors v0.20.1 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.1 // indirect
github.com/go-openapi/loads v0.20.2 // indirect
github.com/go-openapi/runtime v0.19.24 // indirect
github.com/go-openapi/spec v0.20.3 // indirect
github.com/go-openapi/strfmt v0.20.0 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-openapi/validate v0.20.2 // indirect
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/go-test/deep v1.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-metrics-stackdriver v0.2.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/s2a-go v0.1.4 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.9.1 // indirect
github.com/gophercloud/gophercloud v0.1.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/eventlogger v0.2.1 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-discover v0.0.0-20210818145131-c573d69da192 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-kms-wrapping/entropy/v2 v2.0.0 // indirect
github.com/hashicorp/go-kms-wrapping/v2 v2.0.9 // indirect
github.com/hashicorp/go-kms-wrapping/wrappers/aead/v2 v2.0.7-1 // indirect
github.com/hashicorp/go-kms-wrapping/wrappers/alicloudkms/v2 v2.0.1 // indirect
github.com/hashicorp/go-kms-wrapping/wrappers/awskms/v2 v2.0.7 // indirect
github.com/hashicorp/go-kms-wrapping/wrappers/azurekeyvault/v2 v2.0.7 // indirect
github.com/hashicorp/go-kms-wrapping/wrappers/gcpckms/v2 v2.0.8 // indirect
github.com/hashicorp/go-kms-wrapping/wrappers/ocikms/v2 v2.0.7 // indirect
github.com/hashicorp/go-kms-wrapping/wrappers/transit/v2 v2.0.7 // indirect
github.com/hashicorp/go-memdb v1.3.3 // indirect
github.com/hashicorp/go-msgpack v1.1.5 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.4.9 // indirect
github.com/hashicorp/go-raftchunking v0.6.3-0.20191002164813-7e9e8525653a // indirect
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
github.com/hashicorp/go-secure-stdlib/awsutil v0.2.3 // indirect
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 // indirect
github.com/hashicorp/go-secure-stdlib/mlock v0.1.3 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
github.com/hashicorp/hcp-sdk-go v0.23.0 // indirect
github.com/hashicorp/mdns v1.0.4 // indirect
github.com/hashicorp/raft v1.3.10 // indirect
github.com/hashicorp/raft-autopilot v0.2.0 // indirect
github.com/hashicorp/raft-boltdb/v2 v2.0.0-20210421194847-a7e34179d62c // indirect
github.com/hashicorp/raft-snapshot v1.0.4 // indirect
github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.15 // indirect
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f // indirect
github.com/jefferai/jsonx v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kelseyhightower/envconfig v1.4.0 // indirect
github.com/klauspost/compress v1.16.5 // indirect
github.com/linode/linodego v0.7.1 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/miekg/dns v1.1.43 // indirect
github.com/mitchellh/cli v1.1.2 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/okta/okta-sdk-golang/v2 v2.12.1 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/oracle/oci-go-sdk/v60 v60.0.0 // indirect
github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/pires/go-proxyproto v0.6.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/posener/complete v1.2.3 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/pquerna/otp v1.2.1-0.20191009055518-468c2dd2b58d // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rboyer/safeio v0.2.1 // indirect
github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/sasha-s/go-deadlock v0.2.0 // indirect
github.com/sethvargo/go-limiter v0.7.1 // indirect
github.com/shirou/gopsutil/v3 v3.22.6 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d // indirect
github.com/sony/gobreaker v0.4.2-0.20210216022020-dd874f9dd33b // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go v1.0.162 // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.4.0 // indirect
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c // indirect
github.com/vmware/govmomi v0.18.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
go.mongodb.org/mongo-driver v1.11.6 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/sync v0.2.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/api v0.124.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230525154841-bd750badd5c6 // indirect
google.golang.org/grpc v1.56.3 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/resty.v1 v1.12.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.27.2 // indirect
k8s.io/apimachinery v0.27.2 // indirect
k8s.io/client-go v0.27.2 // indirect
k8s.io/klog/v2 v2.90.1 // indirect
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect
nhooyr.io/websocket v1.8.7 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

1672
go.sum Normal file

File diff suppressed because it is too large Load diff

36
main.go Normal file
View file

@ -0,0 +1,36 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) HashiCorp, Inc.
// Copyright (c) Matter Labs
package main
import (
"os"
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/sdk/plugin"
"github.com/matter-labs/vault-auth-tee/tee"
)
func main() {
apiClientMeta := &api.PluginAPIClientMeta{}
flags := apiClientMeta.FlagSet()
flags.Parse(os.Args[1:])
tlsConfig := apiClientMeta.GetTLSConfig()
tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig)
if err := plugin.ServeMultiplex(&plugin.ServeOpts{
BackendFactoryFunc: tee.Factory,
// set the TLSProviderFunc so that the plugin maintains backwards
// compatibility with Vault versions that dont support plugin AutoMTLS
TLSProviderFunc: tlsProviderFunc,
}); err != nil {
logger := hclog.New(&hclog.LoggerOptions{})
logger.Error("plugin shutting down", "error", err)
os.Exit(1)
}
}

530
ratee/sgxquote.go Normal file
View file

@ -0,0 +1,530 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Matter Labs
package ratee
// #cgo LDFLAGS: -lsgx_dcap_quoteverify -ldl
/*
#include <stdlib.h> // for malloc/free
#include <sgx_dcap_quoteverify.h>
#include <sgx_quote.h>
sgx_ql_qv_supplemental_t *allocSupp() { return (sgx_ql_qv_supplemental_t*) malloc(sizeof(sgx_ql_qv_supplemental_t)); }
void freeSupp(sgx_ql_qv_supplemental_t * * p) { free(p); }
*/
import "C"
import (
"bytes"
"encoding/binary"
"errors"
"os"
"unsafe"
)
type TeeQvCollateral struct {
MajorVersion uint16 `json:"major_version"`
MinorVersion uint16 `json:"minor_version"`
TeeType uint32 `json:"tee_type"`
PckCrlIssuerChain []byte `json:"pck_crl_issuer_chain"`
RootCaCrl []byte `json:"root_ca_crl"`
PckCrl []byte `json:"pck_crl"`
TcbInfoIssuerChain []byte `json:"tcb_info_issuer_chain"`
TcbInfo []byte `json:"tcb_info"`
QeIdentityIssuerChain []byte `json:"qe_identity_issuer_chain"`
QeIdentity []byte `json:"qe_identity"`
}
type Quote struct {
Version [2]byte `json:"version"`
KeyType [2]byte `json:"key_type"`
Reserved [4]byte `json:"reserved"`
QeSvn [2]byte `json:"qe_svn"`
PceSvn [2]byte `json:"pce_svn"`
QeVendorId [16]byte `json:"qe_vendor_id"`
UserData [20]byte `json:"user_data"`
ReportBody struct {
Cpusvn [16]byte `json:"cpusvn"`
Miscselect [4]byte `json:"miscselect"`
Reserved1 [28]byte `json:"reserved1"`
Features [8]byte `json:"features"`
Xfrm [8]byte `json:"xfrm"`
MrEnclave [32]byte `json:"mrenclave"`
Reserved2 [32]byte `json:"reserved2"`
MrSigner [32]byte `json:"mrsigner"`
Reserved3 [96]byte `json:"reserved3"`
IsvProdid [2]byte `json:"isv_prodid"`
IsvSvn [2]byte `json:"isv_svn"`
Reserved4 [60]byte `json:"reserved4"`
ReportData [64]byte `json:"reportdata"`
} `json:"report_body"`
}
type QuoteVerificationResult struct {
VerificationResult SgxQlQvResult
CollateralExpired bool
EarliestExpirationDate int64
Advisory string
Quote Quote
}
// convertCollateral converts TeeQvCollateral to sgx_ql_qve_collateral_t
func convertQveCollateral(coll C.sgx_ql_qve_collateral_t) TeeQvCollateral {
var data = TeeQvCollateral{
TeeType: uint32(coll.tee_type),
PckCrlIssuerChain: C.GoBytes(unsafe.Pointer(coll.pck_crl_issuer_chain), C.int(coll.pck_crl_issuer_chain_size)),
RootCaCrl: C.GoBytes(unsafe.Pointer(coll.root_ca_crl), C.int(coll.root_ca_crl_size)),
PckCrl: C.GoBytes(unsafe.Pointer(coll.pck_crl), C.int(coll.pck_crl_size)),
TcbInfoIssuerChain: C.GoBytes(unsafe.Pointer(coll.tcb_info_issuer_chain), C.int(coll.tcb_info_issuer_chain_size)),
TcbInfo: C.GoBytes(unsafe.Pointer(coll.tcb_info), C.int(coll.tcb_info_size)),
QeIdentityIssuerChain: C.GoBytes(unsafe.Pointer(coll.qe_identity_issuer_chain), C.int(coll.qe_identity_issuer_chain_size)),
QeIdentity: C.GoBytes(unsafe.Pointer(coll.qe_identity), C.int(coll.qe_identity_size)),
}
// Hack needed due to unnamed union and struct at the beginning
var pver = (*[2]uint16)(unsafe.Pointer(&coll))
data.MajorVersion = pver[0]
data.MinorVersion = pver[1]
return data
}
// convertCollateral converts TeeQvCollateral to sgx_ql_qve_collateral_t
func convertCollateral(coll TeeQvCollateral) C.sgx_ql_qve_collateral_t {
var data = C.sgx_ql_qve_collateral_t{
tee_type: C.uint32_t(coll.TeeType),
pck_crl_issuer_chain: (*C.char)(C.CBytes(coll.PckCrlIssuerChain)),
pck_crl_issuer_chain_size: C.uint32_t(len(coll.PckCrlIssuerChain)),
root_ca_crl: (*C.char)(C.CBytes(coll.RootCaCrl)),
root_ca_crl_size: C.uint32_t(len(coll.RootCaCrl)),
pck_crl: (*C.char)(C.CBytes(coll.PckCrl)),
pck_crl_size: C.uint32_t(len(coll.PckCrl)),
tcb_info_issuer_chain: (*C.char)(C.CBytes(coll.TcbInfoIssuerChain)),
tcb_info_issuer_chain_size: C.uint32_t(len(coll.TcbInfoIssuerChain)),
tcb_info: (*C.char)(C.CBytes(coll.TcbInfo)),
tcb_info_size: C.uint32_t(len(coll.TcbInfo)),
qe_identity_issuer_chain: (*C.char)(C.CBytes(coll.QeIdentityIssuerChain)),
qe_identity_issuer_chain_size: C.uint32_t(len(coll.QeIdentityIssuerChain)),
qe_identity: (*C.char)(C.CBytes(coll.QeIdentity)),
qe_identity_size: C.uint32_t(len(coll.QeIdentity)),
}
// Hack needed due to unnamed union and struct at the beginning
var pver = (*[2]uint16)(unsafe.Pointer(&data))
pver[0] = coll.MajorVersion
pver[1] = coll.MinorVersion
return data
}
type SgxQlQvResult uint32
const (
SgxQlQvResultOk = SgxQlQvResult(C.SGX_QL_QV_RESULT_OK)
SgxQlQvResultConfigNeeded = SgxQlQvResult(C.SGX_QL_QV_RESULT_CONFIG_NEEDED)
SgxQlQvResultOutOfDate = SgxQlQvResult(C.SGX_QL_QV_RESULT_OUT_OF_DATE)
SgxQlQvResultOutOfDateConfigNeeded = SgxQlQvResult(C.SGX_QL_QV_RESULT_OUT_OF_DATE_CONFIG_NEEDED)
SgxQlQvResultInvalidSignature = SgxQlQvResult(C.SGX_QL_QV_RESULT_INVALID_SIGNATURE)
SgxQlQvResultRevoked = SgxQlQvResult(C.SGX_QL_QV_RESULT_REVOKED)
SgxQlQvResultUnspecified = SgxQlQvResult(C.SGX_QL_QV_RESULT_UNSPECIFIED)
SgxQlQvResultSwHardeningNeeded = SgxQlQvResult(C.SGX_QL_QV_RESULT_SW_HARDENING_NEEDED)
SgxQlQvResultConfigAndSwHardeningNeeded = SgxQlQvResult(C.SGX_QL_QV_RESULT_CONFIG_AND_SW_HARDENING_NEEDED)
)
var (
// ErrEmptyReport is returned by VerifyRemoteReport if reportBytes is empty.
ErrEmptyReport = errors.New("empty report")
ErrSgxQlErrorUnexpected = errors.New("SGX_QL_ERROR_UNEXPECTED")
ErrSgxQlErrorInvalidParameter = errors.New("SGX_QL_ERROR_INVALID_PARAMETER")
ErrSgxQlErrorOutOfMemory = errors.New("SGX_QL_ERROR_OUT_OF_MEMORY")
ErrSgxQlErrorEcdsaIdMismatch = errors.New("SGX_QL_ERROR_ECDSA_ID_MISMATCH")
ErrSgxQlPathnameBufferOverflowError = errors.New("SGX_QL_PATHNAME_BUFFER_OVERFLOW_ERROR")
ErrSgxQlFileAccessError = errors.New("SGX_QL_FILE_ACCESS_ERROR")
ErrSgxQlErrorStoredKey = errors.New("SGX_QL_ERROR_STORED_KEY")
ErrSgxQlErrorPubKeyIdMismatch = errors.New("SGX_QL_ERROR_PUB_KEY_ID_MISMATCH")
ErrSgxQlErrorInvalidPceSigScheme = errors.New("SGX_QL_ERROR_INVALID_PCE_SIG_SCHEME")
ErrSgxQlAttKeyBlobError = errors.New("SGX_QL_ATT_KEY_BLOB_ERROR")
ErrSgxQlUnsupportedAttKeyId = errors.New("SGX_QL_UNSUPPORTED_ATT_KEY_ID")
ErrSgxQlUnsupportedLoadingPolicy = errors.New("SGX_QL_UNSUPPORTED_LOADING_POLICY")
ErrSgxQlInterfaceUnavailable = errors.New("SGX_QL_INTERFACE_UNAVAILABLE")
ErrSgxQlPlatformLibUnavailable = errors.New("SGX_QL_PLATFORM_LIB_UNAVAILABLE")
ErrSgxQlAttKeyNotInitialized = errors.New("SGX_QL_ATT_KEY_NOT_INITIALIZED")
ErrSgxQlAttKeyCertDataInvalid = errors.New("SGX_QL_ATT_KEY_CERT_DATA_INVALID")
ErrSgxQlNoPlatformCertData = errors.New("SGX_QL_NO_PLATFORM_CERT_DATA")
ErrSgxQlOutOfEpc = errors.New("SGX_QL_OUT_OF_EPC")
ErrSgxQlErrorReport = errors.New("SGX_QL_ERROR_REPORT")
ErrSgxQlEnclaveLost = errors.New("SGX_QL_ENCLAVE_LOST")
ErrSgxQlInvalidReport = errors.New("SGX_QL_INVALID_REPORT")
ErrSgxQlEnclaveLoadError = errors.New("SGX_QL_ENCLAVE_LOAD_ERROR")
ErrSgxQlUnableToGenerateQeReport = errors.New("SGX_QL_UNABLE_TO_GENERATE_QE_REPORT")
ErrSgxQlKeyCertifcationError = errors.New("SGX_QL_KEY_CERTIFCATION_ERROR")
ErrSgxQlNetworkError = errors.New("SGX_QL_NETWORK_ERROR")
ErrSgxQlMessageError = errors.New("SGX_QL_MESSAGE_ERROR")
ErrSgxQlNoQuoteCollateralData = errors.New("SGX_QL_NO_QUOTE_COLLATERAL_DATA")
ErrSgxQlQuoteCertificationDataUnsupported = errors.New("SGX_QL_QUOTE_CERTIFICATION_DATA_UNSUPPORTED")
ErrSgxQlQuoteFormatUnsupported = errors.New("SGX_QL_QUOTE_FORMAT_UNSUPPORTED")
ErrSgxQlUnableToGenerateReport = errors.New("SGX_QL_UNABLE_TO_GENERATE_REPORT")
ErrSgxQlQeReportInvalidSignature = errors.New("SGX_QL_QE_REPORT_INVALID_SIGNATURE")
ErrSgxQlQeReportUnsupportedFormat = errors.New("SGX_QL_QE_REPORT_UNSUPPORTED_FORMAT")
ErrSgxQlPckCertUnsupportedFormat = errors.New("SGX_QL_PCK_CERT_UNSUPPORTED_FORMAT")
ErrSgxQlPckCertChainError = errors.New("SGX_QL_PCK_CERT_CHAIN_ERROR")
ErrSgxQlTcbinfoUnsupportedFormat = errors.New("SGX_QL_TCBINFO_UNSUPPORTED_FORMAT")
ErrSgxQlTcbinfoMismatch = errors.New("SGX_QL_TCBINFO_MISMATCH")
ErrSgxQlQeidentityUnsupportedFormat = errors.New("SGX_QL_QEIDENTITY_UNSUPPORTED_FORMAT")
ErrSgxQlQeidentityMismatch = errors.New("SGX_QL_QEIDENTITY_MISMATCH")
ErrSgxQlTcbOutOfDate = errors.New("SGX_QL_TCB_OUT_OF_DATE")
ErrSgxQlTcbOutOfDateConfigurationNeeded = errors.New("SGX_QL_TCB_OUT_OF_DATE_CONFIGURATION_NEEDED")
ErrSgxQlSgxEnclaveIdentityOutOfDate = errors.New("SGX_QL_SGX_ENCLAVE_IDENTITY_OUT_OF_DATE")
ErrSgxQlSgxEnclaveReportIsvsvnOutOfDate = errors.New("SGX_QL_SGX_ENCLAVE_REPORT_ISVSVN_OUT_OF_DATE")
ErrSgxQlQeIdentityOutOfDate = errors.New("SGX_QL_QE_IDENTITY_OUT_OF_DATE")
ErrSgxQlSgxTcbInfoExpired = errors.New("SGX_QL_SGX_TCB_INFO_EXPIRED")
ErrSgxQlSgxPckCertChainExpired = errors.New("SGX_QL_SGX_PCK_CERT_CHAIN_EXPIRED")
ErrSgxQlSgxCrlExpired = errors.New("SGX_QL_SGX_CRL_EXPIRED")
ErrSgxQlSgxSigningCertChainExpired = errors.New("SGX_QL_SGX_SIGNING_CERT_CHAIN_EXPIRED")
ErrSgxQlSgxEnclaveIdentityExpired = errors.New("SGX_QL_SGX_ENCLAVE_IDENTITY_EXPIRED")
ErrSgxQlPckRevoked = errors.New("SGX_QL_PCK_REVOKED")
ErrSgxQlTcbRevoked = errors.New("SGX_QL_TCB_REVOKED")
ErrSgxQlTcbConfigurationNeeded = errors.New("SGX_QL_TCB_CONFIGURATION_NEEDED")
ErrSgxQlUnableToGetCollateral = errors.New("SGX_QL_UNABLE_TO_GET_COLLATERAL")
ErrSgxQlErrorInvalidPrivilege = errors.New("SGX_QL_ERROR_INVALID_PRIVILEGE")
ErrSgxQlNoQveIdentityData = errors.New("SGX_QL_NO_QVE_IDENTITY_DATA")
ErrSgxQlCrlUnsupportedFormat = errors.New("SGX_QL_CRL_UNSUPPORTED_FORMAT")
ErrSgxQlQeidentityChainError = errors.New("SGX_QL_QEIDENTITY_CHAIN_ERROR")
ErrSgxQlTcbinfoChainError = errors.New("SGX_QL_TCBINFO_CHAIN_ERROR")
ErrSgxQlErrorQvlQveMismatch = errors.New("SGX_QL_ERROR_QVL_QVE_MISMATCH")
ErrSgxQlTcbSwHardeningNeeded = errors.New("SGX_QL_TCB_SW_HARDENING_NEEDED")
ErrSgxQlTcbConfigurationAndSwHardeningNeeded = errors.New("SGX_QL_TCB_CONFIGURATION_AND_SW_HARDENING_NEEDED")
ErrSgxQlUnsupportedMode = errors.New("SGX_QL_UNSUPPORTED_MODE")
ErrSgxQlNoDevice = errors.New("SGX_QL_NO_DEVICE")
ErrSgxQlServiceUnavailable = errors.New("SGX_QL_SERVICE_UNAVAILABLE")
ErrSgxQlNetworkFailure = errors.New("SGX_QL_NETWORK_FAILURE")
ErrSgxQlServiceTimeout = errors.New("SGX_QL_SERVICE_TIMEOUT")
ErrSgxQlErrorBusy = errors.New("SGX_QL_ERROR_BUSY")
ErrSgxQlUnknownMessageResponse = errors.New("SGX_QL_UNKNOWN_MESSAGE_RESPONSE")
ErrSgxQlPersistentStorageError = errors.New("SGX_QL_PERSISTENT_STORAGE_ERROR")
ErrSgxQlErrorMessageParsingError = errors.New("SGX_QL_ERROR_MESSAGE_PARSING_ERROR")
ErrSgxQlPlatformUnknown = errors.New("SGX_QL_PLATFORM_UNKNOWN")
ErrSgxQlUnknownApiVersion = errors.New("SGX_QL_UNKNOWN_API_VERSION")
ErrSgxQlCertsUnavailable = errors.New("SGX_QL_CERTS_UNAVAILABLE")
ErrSgxQlQveidentityMismatch = errors.New("SGX_QL_QVEIDENTITY_MISMATCH")
ErrSgxQlQveOutOfDate = errors.New("SGX_QL_QVE_OUT_OF_DATE")
ErrSgxQlPswNotAvailable = errors.New("SGX_QL_PSW_NOT_AVAILABLE")
ErrSgxQlCollateralVersionNotSupported = errors.New("SGX_QL_COLLATERAL_VERSION_NOT_SUPPORTED")
ErrSgxQlTdxModuleMismatch = errors.New("SGX_QL_TDX_MODULE_MISMATCH")
ErrSgxQlQeidentityNotFound = errors.New("SGX_QL_QEIDENTITY_NOT_FOUND")
ErrSgxQlTcbinfoNotFound = errors.New("SGX_QL_TCBINFO_NOT_FOUND")
ErrSgxQlInternalServerError = errors.New("SGX_QL_INTERNAL_SERVER_ERROR")
ErrSgxQlSupplementalDataVersionNotSupported = errors.New("SGX_QL_SUPPLEMENTAL_DATA_VERSION_NOT_SUPPORTED")
ErrSgxQlRootCaUntrusted = errors.New("SGX_QL_ROOT_CA_UNTRUSTED")
ErrSgxQlTcbNotSupported = errors.New("SGX_QL_TCB_NOT_SUPPORTED")
)
// convert an SGX quote library error to a go error
func sgx2Error(err uint32) error {
switch err {
case C.SGX_QL_ERROR_UNEXPECTED:
return ErrSgxQlErrorUnexpected
case C.SGX_QL_ERROR_INVALID_PARAMETER:
return ErrSgxQlErrorInvalidParameter
case C.SGX_QL_ERROR_OUT_OF_MEMORY:
return ErrSgxQlErrorOutOfMemory
case C.SGX_QL_ERROR_ECDSA_ID_MISMATCH:
return ErrSgxQlErrorEcdsaIdMismatch
case C.SGX_QL_PATHNAME_BUFFER_OVERFLOW_ERROR:
return ErrSgxQlPathnameBufferOverflowError
case C.SGX_QL_FILE_ACCESS_ERROR:
return ErrSgxQlFileAccessError
case C.SGX_QL_ERROR_STORED_KEY:
return ErrSgxQlErrorStoredKey
case C.SGX_QL_ERROR_PUB_KEY_ID_MISMATCH:
return ErrSgxQlErrorPubKeyIdMismatch
case C.SGX_QL_ERROR_INVALID_PCE_SIG_SCHEME:
return ErrSgxQlErrorInvalidPceSigScheme
case C.SGX_QL_ATT_KEY_BLOB_ERROR:
return ErrSgxQlAttKeyBlobError
case C.SGX_QL_UNSUPPORTED_ATT_KEY_ID:
return ErrSgxQlUnsupportedAttKeyId
case C.SGX_QL_UNSUPPORTED_LOADING_POLICY:
return ErrSgxQlUnsupportedLoadingPolicy
case C.SGX_QL_INTERFACE_UNAVAILABLE:
return ErrSgxQlInterfaceUnavailable
case C.SGX_QL_PLATFORM_LIB_UNAVAILABLE:
return ErrSgxQlPlatformLibUnavailable
case C.SGX_QL_ATT_KEY_NOT_INITIALIZED:
return ErrSgxQlAttKeyNotInitialized
case C.SGX_QL_ATT_KEY_CERT_DATA_INVALID:
return ErrSgxQlAttKeyCertDataInvalid
case C.SGX_QL_NO_PLATFORM_CERT_DATA:
return ErrSgxQlNoPlatformCertData
case C.SGX_QL_OUT_OF_EPC:
return ErrSgxQlOutOfEpc
case C.SGX_QL_ERROR_REPORT:
return ErrSgxQlErrorReport
case C.SGX_QL_ENCLAVE_LOST:
return ErrSgxQlEnclaveLost
case C.SGX_QL_INVALID_REPORT:
return ErrSgxQlInvalidReport
case C.SGX_QL_ENCLAVE_LOAD_ERROR:
return ErrSgxQlEnclaveLoadError
case C.SGX_QL_UNABLE_TO_GENERATE_QE_REPORT:
return ErrSgxQlUnableToGenerateQeReport
case C.SGX_QL_KEY_CERTIFCATION_ERROR:
return ErrSgxQlKeyCertifcationError
case C.SGX_QL_NETWORK_ERROR:
return ErrSgxQlNetworkError
case C.SGX_QL_MESSAGE_ERROR:
return ErrSgxQlMessageError
case C.SGX_QL_NO_QUOTE_COLLATERAL_DATA:
return ErrSgxQlNoQuoteCollateralData
case C.SGX_QL_QUOTE_CERTIFICATION_DATA_UNSUPPORTED:
return ErrSgxQlQuoteCertificationDataUnsupported
case C.SGX_QL_QUOTE_FORMAT_UNSUPPORTED:
return ErrSgxQlQuoteFormatUnsupported
case C.SGX_QL_UNABLE_TO_GENERATE_REPORT:
return ErrSgxQlUnableToGenerateReport
case C.SGX_QL_QE_REPORT_INVALID_SIGNATURE:
return ErrSgxQlQeReportInvalidSignature
case C.SGX_QL_QE_REPORT_UNSUPPORTED_FORMAT:
return ErrSgxQlQeReportUnsupportedFormat
case C.SGX_QL_PCK_CERT_UNSUPPORTED_FORMAT:
return ErrSgxQlPckCertUnsupportedFormat
case C.SGX_QL_PCK_CERT_CHAIN_ERROR:
return ErrSgxQlPckCertChainError
case C.SGX_QL_TCBINFO_UNSUPPORTED_FORMAT:
return ErrSgxQlTcbinfoUnsupportedFormat
case C.SGX_QL_TCBINFO_MISMATCH:
return ErrSgxQlTcbinfoMismatch
case C.SGX_QL_QEIDENTITY_UNSUPPORTED_FORMAT:
return ErrSgxQlQeidentityUnsupportedFormat
case C.SGX_QL_QEIDENTITY_MISMATCH:
return ErrSgxQlQeidentityMismatch
case C.SGX_QL_TCB_OUT_OF_DATE:
return ErrSgxQlTcbOutOfDate
case C.SGX_QL_TCB_OUT_OF_DATE_CONFIGURATION_NEEDED:
return ErrSgxQlTcbOutOfDateConfigurationNeeded
case C.SGX_QL_SGX_ENCLAVE_IDENTITY_OUT_OF_DATE:
return ErrSgxQlSgxEnclaveIdentityOutOfDate
case C.SGX_QL_SGX_ENCLAVE_REPORT_ISVSVN_OUT_OF_DATE:
return ErrSgxQlSgxEnclaveReportIsvsvnOutOfDate
case C.SGX_QL_QE_IDENTITY_OUT_OF_DATE:
return ErrSgxQlQeIdentityOutOfDate
case C.SGX_QL_SGX_TCB_INFO_EXPIRED:
return ErrSgxQlSgxTcbInfoExpired
case C.SGX_QL_SGX_PCK_CERT_CHAIN_EXPIRED:
return ErrSgxQlSgxPckCertChainExpired
case C.SGX_QL_SGX_CRL_EXPIRED:
return ErrSgxQlSgxCrlExpired
case C.SGX_QL_SGX_SIGNING_CERT_CHAIN_EXPIRED:
return ErrSgxQlSgxSigningCertChainExpired
case C.SGX_QL_SGX_ENCLAVE_IDENTITY_EXPIRED:
return ErrSgxQlSgxEnclaveIdentityExpired
case C.SGX_QL_PCK_REVOKED:
return ErrSgxQlPckRevoked
case C.SGX_QL_TCB_REVOKED:
return ErrSgxQlTcbRevoked
case C.SGX_QL_TCB_CONFIGURATION_NEEDED:
return ErrSgxQlTcbConfigurationNeeded
case C.SGX_QL_UNABLE_TO_GET_COLLATERAL:
return ErrSgxQlUnableToGetCollateral
case C.SGX_QL_ERROR_INVALID_PRIVILEGE:
return ErrSgxQlErrorInvalidPrivilege
case C.SGX_QL_NO_QVE_IDENTITY_DATA:
return ErrSgxQlNoQveIdentityData
case C.SGX_QL_CRL_UNSUPPORTED_FORMAT:
return ErrSgxQlCrlUnsupportedFormat
case C.SGX_QL_QEIDENTITY_CHAIN_ERROR:
return ErrSgxQlQeidentityChainError
case C.SGX_QL_TCBINFO_CHAIN_ERROR:
return ErrSgxQlTcbinfoChainError
case C.SGX_QL_ERROR_QVL_QVE_MISMATCH:
return ErrSgxQlErrorQvlQveMismatch
case C.SGX_QL_TCB_SW_HARDENING_NEEDED:
return ErrSgxQlTcbSwHardeningNeeded
case C.SGX_QL_TCB_CONFIGURATION_AND_SW_HARDENING_NEEDED:
return ErrSgxQlTcbConfigurationAndSwHardeningNeeded
case C.SGX_QL_UNSUPPORTED_MODE:
return ErrSgxQlUnsupportedMode
case C.SGX_QL_NO_DEVICE:
return ErrSgxQlNoDevice
case C.SGX_QL_SERVICE_UNAVAILABLE:
return ErrSgxQlServiceUnavailable
case C.SGX_QL_NETWORK_FAILURE:
return ErrSgxQlNetworkFailure
case C.SGX_QL_SERVICE_TIMEOUT:
return ErrSgxQlServiceTimeout
case C.SGX_QL_ERROR_BUSY:
return ErrSgxQlErrorBusy
case C.SGX_QL_UNKNOWN_MESSAGE_RESPONSE:
return ErrSgxQlUnknownMessageResponse
case C.SGX_QL_PERSISTENT_STORAGE_ERROR:
return ErrSgxQlPersistentStorageError
case C.SGX_QL_ERROR_MESSAGE_PARSING_ERROR:
return ErrSgxQlErrorMessageParsingError
case C.SGX_QL_PLATFORM_UNKNOWN:
return ErrSgxQlPlatformUnknown
case C.SGX_QL_UNKNOWN_API_VERSION:
return ErrSgxQlUnknownApiVersion
case C.SGX_QL_CERTS_UNAVAILABLE:
return ErrSgxQlCertsUnavailable
case C.SGX_QL_QVEIDENTITY_MISMATCH:
return ErrSgxQlQveidentityMismatch
case C.SGX_QL_QVE_OUT_OF_DATE:
return ErrSgxQlQveOutOfDate
case C.SGX_QL_PSW_NOT_AVAILABLE:
return ErrSgxQlPswNotAvailable
case C.SGX_QL_COLLATERAL_VERSION_NOT_SUPPORTED:
return ErrSgxQlCollateralVersionNotSupported
case C.SGX_QL_TDX_MODULE_MISMATCH:
return ErrSgxQlTdxModuleMismatch
case C.SGX_QL_QEIDENTITY_NOT_FOUND:
return ErrSgxQlQeidentityNotFound
case C.SGX_QL_TCBINFO_NOT_FOUND:
return ErrSgxQlTcbinfoNotFound
case C.SGX_QL_INTERNAL_SERVER_ERROR:
return ErrSgxQlInternalServerError
case C.SGX_QL_SUPPLEMENTAL_DATA_VERSION_NOT_SUPPORTED:
return ErrSgxQlSupplementalDataVersionNotSupported
case C.SGX_QL_ROOT_CA_UNTRUSTED:
return ErrSgxQlRootCaUntrusted
case C.SGX_QL_TCB_NOT_SUPPORTED:
return ErrSgxQlTcbNotSupported
}
return ErrSgxQlErrorUnexpected
}
func verifyRemoteReportSGXCollateral(reportBytes []byte, pQuoteCollateral *C.sgx_ql_qve_collateral_t, expirationCheckDate int64) (*QuoteVerificationResult, error) {
if len(reportBytes) == 0 {
return nil, ErrEmptyReport
}
var collateralExpirationStatus uint32
var quoteVerificationResult uint32
var pSuppData = C.allocSupp()
var suppDataDesc = C.tee_supp_data_descriptor_t{
major_version: 0,
data_size: C.uint(unsafe.Sizeof(C.sgx_ql_qv_supplemental_t{})),
p_data: (*C.uchar)(unsafe.Pointer(pSuppData)),
}
var pReportBytes = C.CBytes(reportBytes)
res := uint32(C.tee_verify_quote(
(*C.uint8_t)(pReportBytes),
C.uint32_t(len(reportBytes)),
(*C.uint8_t)(unsafe.Pointer(pQuoteCollateral)),
C.time_t(expirationCheckDate),
(*C.uint32_t)(&collateralExpirationStatus),
(*C.sgx_ql_qv_result_t)(&quoteVerificationResult),
nil,
&suppDataDesc,
))
if res != C.SGX_QL_SUCCESS {
return nil, sgx2Error(res)
}
var quote = Quote{}
var byteReader = bytes.NewReader(reportBytes)
err := binary.Read(byteReader, binary.BigEndian, &quote)
if err != nil {
panic(err)
}
var ret = QuoteVerificationResult{
VerificationResult: SgxQlQvResult(quoteVerificationResult),
CollateralExpired: collateralExpirationStatus != 0,
EarliestExpirationDate: int64(pSuppData.earliest_expiration_date),
Quote: quote,
}
return &ret, nil
}
// SgxVerifyRemoteReport verifies the SGX attestation report.
// It needs to connect to servers to collect the collateral material.
func SgxVerifyRemoteReport(reportBytes []byte, expirationCheckDate int64) (*QuoteVerificationResult, error) {
if len(reportBytes) == 0 {
return nil, ErrEmptyReport
}
var pQuoteCollateral *C.uint8_t = nil
var collateralSize C.uint32_t
res := uint32(C.tee_qv_get_collateral(
(*C.uint8_t)(&reportBytes[0]),
C.uint32_t(len(reportBytes)),
(**C.uint8_t)(&pQuoteCollateral),
(*C.uint32_t)(&collateralSize),
))
defer C.tee_qv_free_collateral(pQuoteCollateral)
if res != C.SGX_QL_SUCCESS {
return nil, sgx2Error(res)
}
return verifyRemoteReportSGXCollateral(reportBytes, (*C.sgx_ql_qve_collateral_t)(unsafe.Pointer(pQuoteCollateral)), expirationCheckDate)
}
// SgxVerifyRemoteReportCollateral verifies the report along with the collateral material.
// It does not need to start an SGX enclave, nor does it need to connect to any server.
func SgxVerifyRemoteReportCollateral(reportBytes []byte, collateral TeeQvCollateral, expirationCheckDate int64) (*QuoteVerificationResult, error) {
var quoteCollateral = convertCollateral(collateral)
return verifyRemoteReportSGXCollateral(reportBytes, &quoteCollateral, expirationCheckDate)
}
func SgxGetCollateral(reportBytes []byte) (*TeeQvCollateral, error) {
var pQuoteCollateral *C.uint8_t = nil
var collateralSize C.uint32_t
res := uint32(C.tee_qv_get_collateral(
(*C.uint8_t)(&reportBytes[0]),
C.uint32_t(len(reportBytes)),
(**C.uint8_t)(&pQuoteCollateral),
(*C.uint32_t)(&collateralSize),
))
defer C.tee_qv_free_collateral(pQuoteCollateral)
if res != C.SGX_QL_SUCCESS {
return nil, sgx2Error(res)
}
coll := convertQveCollateral(*(*C.sgx_ql_qve_collateral_t)(unsafe.Pointer(pQuoteCollateral)))
return &coll, nil
}
func sgxGramineGetQuote(reportData []byte) ([]byte, error) {
// open "/dev/attestation/user_report_data" and write reportData
if err := os.WriteFile("/dev/attestation/user_report_data", reportData, 0600); err != nil {
return nil, err
}
// open "/dev/attestation/quote" and read quote
quote, err := os.ReadFile("/dev/attestation/quote")
if err != nil {
return nil, err
}
return quote, nil
}
func SgxGetQuote(reportData []byte) ([]byte, error) {
if len(reportData) > 64 {
reportData = reportData[:64]
}
if len(reportData) < 64 {
reportData = append(reportData, make([]byte, 64-len(reportData))...)
}
// only support Gramine for now
// check if "/dev/attestation/user_report_data" and "/dev/attestation/quote" exist
if _, err := os.Stat("/dev/attestation/user_report_data"); err != nil {
return nil, err
}
if _, err := os.Stat("/dev/attestation/quote"); err != nil {
return nil, err
}
return sgxGramineGetQuote(reportData)
}

1491
ratee/sgxquote_test.go Normal file

File diff suppressed because one or more lines are too long

6
renovate.json Normal file
View file

@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
]
}

61
tee/backend.go Normal file
View file

@ -0,0 +1,61 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) HashiCorp, Inc.
// Copyright (c) Matter Labs
package tee
import (
"context"
"github.com/matter-labs/vault-auth-tee/version"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
const operationPrefixTee = "tee"
func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
b := Backend()
if err := b.Setup(ctx, conf); err != nil {
return nil, err
}
return b, nil
}
func Backend() *backend {
var b backend
b.Backend = &framework.Backend{
Help: backendHelp,
PathsSpecial: &logical.Paths{
Unauthenticated: []string{
"login",
},
},
Paths: []*framework.Path{
pathInfo(&b),
pathLogin(&b),
pathListTees(&b),
pathTees(&b),
},
AuthRenew: b.loginPathWrapper(b.pathLoginRenew),
BackendType: logical.TypeCredential,
RunningVersion: "v" + version.Version,
}
return &b
}
type backend struct {
*framework.Backend
}
const backendHelp = `
The "tee" credential provider allows authentication using
remote attestation verification together with TLS client certificates.
A client connects to Vault and uses the "login" endpoint to generate a client token.
Trusted execution environments are configured using the "tees/" endpoint
by a user with root access. Authentication is then done
by supplying the attestation report, the attestation collateral
and the client certificate for "login".
`

284
tee/backend_test.go Normal file

File diff suppressed because one or more lines are too long

40
tee/path_info.go Normal file
View file

@ -0,0 +1,40 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) HashiCorp, Inc.
// Copyright (c) Matter Labs
package tee
import (
"context"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
"github.com/matter-labs/vault-auth-tee/version"
)
func pathInfo(b *backend) *framework.Path {
return &framework.Path{
Pattern: "info",
HelpSynopsis: "Display information about the plugin",
HelpDescription: `
Displays information about the plugin, such as the plugin version and where to
get help.
`,
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathInfoRead,
},
}
}
// pathInfoRead corresponds to READ auth/tee/info.
func (b *backend) pathInfoRead(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
return &logical.Response{
Data: map[string]interface{}{
"name": version.Name,
"version": version.Version,
},
}, nil
}

417
tee/path_login.go Normal file
View file

@ -0,0 +1,417 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) HashiCorp, Inc.
// Copyright (c) Matter Labs
package tee
import (
"bytes"
"context"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/cidrutil"
"github.com/hashicorp/vault/sdk/helper/policyutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/matter-labs/vault-auth-tee/ratee"
)
var timeNowFunc = time.Now
func pathLogin(b *backend) *framework.Path {
return &framework.Path{
Pattern: "login",
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixTee,
OperationVerb: "login",
},
Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
Description: "The name of the tee role to authenticate against.",
},
"type": {
Type: framework.TypeString,
Description: "The type of the TEE.",
},
"quote": {
Type: framework.TypeString,
Description: "The quote Base64 encoded.",
},
"collateral": {
Type: framework.TypeString,
Description: "The collateral Json encoded.",
},
"challenge": {
Type: framework.TypeString,
Description: "Hex encoded bytes to include in the attestation report of the vault quote",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: b.loginPathWrapper(b.pathLogin),
logical.AliasLookaheadOperation: b.pathLoginAliasLookahead,
logical.ResolveRoleOperation: b.loginPathWrapper(b.pathLoginResolveRole),
},
}
}
func (b *backend) loginPathWrapper(wrappedOp func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error)) framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
return wrappedOp(ctx, req, data)
}
}
func (b *backend) pathLoginResolveRole(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
quoteBase64 := data.Get("quote").(string)
if quoteBase64 == "" {
return nil, fmt.Errorf("missing quote")
}
quoteBytes, err := base64.StdEncoding.DecodeString(quoteBase64)
if err != nil {
return logical.ErrorResponse("quote decode error"), nil
}
var quote = ratee.Quote{}
var byteReader = bytes.NewReader(quoteBytes)
err = binary.Read(byteReader, binary.BigEndian, &quote)
if err != nil {
return logical.ErrorResponse("quote decode error"), nil
}
names, err := req.Storage.List(ctx, "tee/")
if err != nil {
return logical.ErrorResponse("no TEE was matched by this request"), nil
}
rb := quote.ReportBody
mrSignerHex := hex.EncodeToString(rb.MrSigner[:])
mrEnclaveHex := hex.EncodeToString(rb.MrEnclave[:])
for _, name := range names {
entry, err := b.Tee(ctx, req.Storage, strings.TrimPrefix(name, "tee/"))
if err != nil {
b.Logger().Error("failed to load trusted tee", "name", name, "error", err)
continue
}
if entry == nil {
// This could happen when the name was provided and the tee doesn't exist,
// or just if between the LIST and the GET the tee was deleted.
continue
}
if entry.SgxMrsigner != "" && entry.SgxMrsigner != mrSignerHex {
continue
}
if entry.SgxMrenclave != "" && entry.SgxMrenclave != mrEnclaveHex {
continue
}
if entry.SgxIsvProdid != int(binary.LittleEndian.Uint16(rb.IsvProdid[:])) {
continue
}
return logical.ResolveRoleResponse(name)
}
return logical.ErrorResponse("no TEE was matched by this request"), nil
}
func (b *backend) pathLoginAliasLookahead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
name := d.Get("name").(string)
if name == "" {
return nil, fmt.Errorf("missing name")
}
return &logical.Response{
Auth: &logical.Auth{
Alias: &logical.Alias{
Name: name,
},
},
}, nil
}
func hashPublicKey256(pub interface{}) ([]byte, error) {
pubBytes, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
return nil, err
}
result := sha256.Sum256(pubBytes)
return result[:], nil
}
func Contains[T comparable](s []T, e T) bool {
for _, v := range s {
if v == e {
return true
}
}
return false
}
func (b *backend) pathLogin(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
name := data.Get("name").(string)
if name == "" {
return nil, fmt.Errorf("missing name")
}
// Allow constraining the login request to a single TeeEntry
entry, err := b.Tee(ctx, req.Storage, strings.TrimPrefix(name, "tee/"))
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("no TEE matching for this login name; additionally got errors during verification: %v", err)), nil
}
if entry == nil {
return logical.ErrorResponse(fmt.Sprintf("no TEE matching for this login name")), nil
}
// Get the connection state
if req.Connection == nil || req.Connection.ConnState == nil {
return logical.ErrorResponse("tls connection required"), nil
}
connState := req.Connection.ConnState
if connState.PeerCertificates == nil || len(connState.PeerCertificates) == 0 {
return logical.ErrorResponse("client certificate must be supplied"), nil
}
clientCert := connState.PeerCertificates[0]
// verify self-signed certificate
roots := x509.NewCertPool()
roots.AddCert(clientCert)
_, err = clientCert.Verify(x509.VerifyOptions{Roots: roots})
if err != nil {
return logical.ErrorResponse("client certificate must be self-signed"), nil
}
if len(entry.TokenBoundCIDRs) > 0 {
if req.Connection == nil {
b.Logger().Warn("token bound CIDRs found but no connection information available for validation")
return nil, logical.ErrPermissionDenied
}
if !cidrutil.RemoteAddrIsOk(req.Connection.RemoteAddr, entry.TokenBoundCIDRs) {
return nil, logical.ErrPermissionDenied
}
}
if clientCert.PublicKey == nil {
return logical.ErrorResponse("no public key found in client certificate"), nil
}
hashClientPk, err := hashPublicKey256(clientCert.PublicKey)
if err != nil {
return logical.ErrorResponse("error hashing public key"), nil
}
teeType := data.Get("type").(string)
if _, ok := entry.Types[teeType]; !ok {
return logical.ErrorResponse(fmt.Sprintf("type `%s` not supported for `%s`", teeType, name)), nil
}
quote := data.Get("quote").(string)
quoteBytes, err := base64.StdEncoding.DecodeString(quote)
if err != nil {
return logical.ErrorResponse("quote decode error"), nil
}
// Do a quick check of the quote before doing the expensive verification
var quoteStart = ratee.Quote{}
var byteReader = bytes.NewReader(quoteBytes)
err = binary.Read(byteReader, binary.BigEndian, &quoteStart)
if err != nil {
return logical.ErrorResponse("quote decode error"), nil
}
reportBody := quoteStart.ReportBody
if !bytes.Equal(reportBody.ReportData[:32], hashClientPk) {
return logical.ErrorResponse("client certificate's hashed public key not in report data of attestation quote report"), nil
}
mrSignerHex := hex.EncodeToString(reportBody.MrSigner[:])
mrEnclaveHex := hex.EncodeToString(reportBody.MrEnclave[:])
if entry.SgxMrsigner != "" && entry.SgxMrsigner != mrSignerHex {
return logical.ErrorResponse("`sgx_mrsigner` does not match"), nil
}
if entry.SgxMrenclave != "" && entry.SgxMrenclave != mrEnclaveHex {
return logical.ErrorResponse("`sgx_mrenclave` does not match"), nil
}
if entry.SgxIsvProdid != int(binary.LittleEndian.Uint16(reportBody.IsvProdid[:])) {
return logical.ErrorResponse("`sgx_isv_prodid` does not match"), nil
}
if entry.SgxMinIsvSvn > int(binary.LittleEndian.Uint16(reportBody.IsvSvn[:])) {
return logical.ErrorResponse("`sgx_isv_svn` too low"), nil
}
// Decode the collateral
jsonCollateralBlob := data.Get("collateral").(string)
var collateral ratee.TeeQvCollateral
err = json.Unmarshal([]byte(jsonCollateralBlob), &collateral)
if err != nil {
return logical.ErrorResponse("collateral unmarshal error"), nil
}
// Do the actual remote attestation verification
result, err := ratee.SgxVerifyRemoteReportCollateral(quoteBytes, collateral, timeNowFunc().Unix())
if err != nil {
return logical.ErrorResponse("sgx verify error"), nil
}
if result.CollateralExpired {
return logical.ErrorResponse("collateral expired"), nil
}
if result.VerificationResult != ratee.SgxQlQvResultOk {
if entry.SgxAllowedTcbLevels[result.VerificationResult] != true {
return logical.ErrorResponse("invalid TCB state %v", result.VerificationResult), nil
}
}
skid := base64.StdEncoding.EncodeToString(clientCert.SubjectKeyId)
akid := base64.StdEncoding.EncodeToString(clientCert.AuthorityKeyId)
pkid := base64.StdEncoding.EncodeToString(hashClientPk)
expirationDate := time.Unix(result.EarliestExpirationDate, 0)
metadata := map[string]string{
"tee_name": entry.Name,
"collateral_expiration_date": expirationDate.Format(time.RFC3339),
}
auth := &logical.Auth{
InternalData: map[string]interface{}{
"subject_key_id": skid,
"authority_key_id": akid,
"hash_public_key": pkid,
},
Alias: &logical.Alias{
Name: entry.Name,
},
DisplayName: entry.DisplayName,
Metadata: metadata,
}
entry.PopulateTokenAuth(auth)
now := timeNowFunc()
if !now.Add(auth.TTL).After(expirationDate) {
auth.TTL = expirationDate.Sub(now)
}
if !now.Add(auth.MaxTTL).After(expirationDate) {
auth.MaxTTL = expirationDate.Sub(now)
}
respData := make(map[string]interface{})
challenge := data.Get("challenge").(string)
if challenge != "" {
challengeBytes, err := hex.DecodeString(challenge)
if err != nil {
return logical.ErrorResponse("challenge decode error"), nil
}
ourQuote, err := ratee.SgxGetQuote(challengeBytes)
if err != nil {
return logical.ErrorResponse("vault quote error"), nil
}
quoteBase64 := base64.StdEncoding.EncodeToString(ourQuote)
respData["quote"] = quoteBase64
collateral, err := ratee.SgxGetCollateral(ourQuote)
if err != nil {
return logical.ErrorResponse("vault collateral error"), nil
}
collateralJson, err := json.Marshal(collateral)
if err != nil {
return logical.ErrorResponse("vault collateral json error"), nil
}
respData["collateral"] = string(collateralJson)
}
return &logical.Response{
Auth: auth,
Data: respData,
}, nil
}
func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
clientCerts := req.Connection.ConnState.PeerCertificates
if len(clientCerts) == 0 {
return logical.ErrorResponse("no client certificate found"), nil
}
hashClientPk, err := hashPublicKey256(clientCerts[0].PublicKey)
if err != nil {
return logical.ErrorResponse("error hashing public key"), nil
}
skid := base64.StdEncoding.EncodeToString(clientCerts[0].SubjectKeyId)
akid := base64.StdEncoding.EncodeToString(clientCerts[0].AuthorityKeyId)
pkid := base64.StdEncoding.EncodeToString(hashClientPk)
// Certificate should not only match a registered tee policy.
// Also, the identity of the certificate presented should match the identity of the certificate used during login
if req.Auth.InternalData["subject_key_id"] != skid && req.Auth.InternalData["authority_key_id"] != akid && req.Auth.InternalData["hash_public_key"] != pkid {
return nil, fmt.Errorf("client identity during renewal not matching client identity used during login")
}
// Get the tee and use its TTL
tee, err := b.Tee(ctx, req.Storage, req.Auth.Metadata["tee_name"])
if err != nil {
return nil, err
}
if tee == nil {
// User no longer exists, do not renew
return nil, nil
}
if !policyutil.EquivalentPolicies(tee.TokenPolicies, req.Auth.TokenPolicies) {
return nil, fmt.Errorf("policies have changed, not renewing")
}
expirationDate, err := time.Parse(time.RFC3339, req.Auth.Metadata["collateral_expiration_date"])
if err != nil {
return logical.ErrorResponse("error parsing `collateral_expiration_date` metadata"), nil
}
now := timeNowFunc()
if expirationDate.Before(now) {
return logical.ErrorResponse("Collateral expired"), nil
}
resp := &logical.Response{Auth: req.Auth}
fmt.Errorf("XXXXXXXX: tee.TokenTTL: %v\n", tee.TokenTTL)
if now.Add(tee.TokenTTL).After(expirationDate) {
resp.Auth.TTL = tee.TokenTTL
} else {
resp.Auth.TTL = expirationDate.Sub(now)
}
if now.Add(tee.TokenMaxTTL).After(expirationDate) {
resp.Auth.MaxTTL = tee.TokenMaxTTL
} else {
resp.Auth.MaxTTL = expirationDate.Sub(now)
}
resp.Auth.Period = tee.TokenPeriod
return resp, nil
}

145
tee/path_login_test.go Normal file

File diff suppressed because one or more lines are too long

331
tee/path_tees.go Normal file
View file

@ -0,0 +1,331 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) HashiCorp, Inc.
// Copyright (c) Matter Labs
package tee
import "C"
import (
"context"
"encoding/hex"
"fmt"
"strings"
"time"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/tokenutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/matter-labs/vault-auth-tee/ratee"
)
func pathListTees(b *backend) *framework.Path {
return &framework.Path{
Pattern: "tees/?",
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixTee,
OperationSuffix: "tees",
Navigation: true,
ItemType: "Tee",
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ListOperation: b.pathTeeList,
},
HelpSynopsis: pathTeeHelpSyn,
HelpDescription: pathTeeHelpDesc,
}
}
func pathTees(b *backend) *framework.Path {
p := &framework.Path{
Pattern: "tees/" + framework.GenericNameRegex("name"),
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixTee,
OperationSuffix: "tee",
Action: "Create",
ItemType: "Tee",
},
Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
Description: "The name of the Tee, which passes remote attestation verification",
},
"types": {
Type: framework.TypeCommaStringSlice,
Description: "The types of the TEE.",
},
"sgx_mrsigner": {
Type: framework.TypeString,
Description: `The SGX mrsigner hex value to check the attestation report against`,
},
"sgx_mrenclave": {
Type: framework.TypeString,
Description: `The SGX mrenclave hex value to check the attestation report against`,
},
"sgx_isv_prodid": {
Type: framework.TypeInt,
Description: `The SGX isv_prodid value to check the attestation report against`,
},
"sgx_min_isv_svn": {
Type: framework.TypeInt,
Description: `The SGX minimum isv_svn value to check the attestation report against`,
},
"sgx_allowed_tcb_levels": {
Type: framework.TypeCommaStringSlice,
Description: `A comma seperated list of allowed SGX TCB states.
Allowed values are: ConfigNeeded, OutOfDate, OutOfDateConfigNeeded, SwHardeningNeeded, ConfigAndSwHardeningNeeded`,
},
"display_name": {
Type: framework.TypeString,
Description: `The display name to use for clients using this certificate.`,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.DeleteOperation: b.pathTeeDelete,
logical.ReadOperation: b.pathTeeRead,
logical.UpdateOperation: b.pathTeeWrite,
},
HelpSynopsis: pathTeeHelpSyn,
HelpDescription: pathTeeHelpDesc,
}
tokenutil.AddTokenFields(p.Fields)
return p
}
func (b *backend) Tee(ctx context.Context, s logical.Storage, n string) (*TeeEntry, error) {
entry, err := s.Get(ctx, "tee/"+strings.ToLower(n))
if err != nil {
return nil, err
}
if entry == nil {
return nil, nil
}
var result TeeEntry
if err := entry.DecodeJSON(&result); err != nil {
return nil, err
}
return &result, nil
}
func (b *backend) pathTeeDelete(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
err := req.Storage.Delete(ctx, "tee/"+strings.ToLower(d.Get("name").(string)))
if err != nil {
return nil, err
}
return nil, nil
}
func (b *backend) pathTeeList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
tees, err := req.Storage.List(ctx, "tee/")
if err != nil {
return nil, err
}
return logical.ListResponse(tees), nil
}
func (b *backend) pathTeeRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
tee, err := b.Tee(ctx, req.Storage, strings.ToLower(d.Get("name").(string)))
if err != nil {
return nil, err
}
if tee == nil {
return nil, nil
}
data := map[string]interface{}{
"display_name": tee.DisplayName,
"types": tee.Types,
"sgx_mrsigner": tee.SgxMrsigner,
"sgx_mrenclave": tee.SgxMrenclave,
"sgx_isv_prodid": tee.SgxIsvProdid,
"sgx_min_isv_svn": tee.SgxMinIsvSvn,
"sgx_allowed_tcb_levels": tee.SgxAllowedTcbLevels,
}
tee.PopulateTokenData(data)
return &logical.Response{
Data: data,
}, nil
}
func (b *backend) pathTeeWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
name := strings.ToLower(d.Get("name").(string))
tee, err := b.Tee(ctx, req.Storage, name)
if err != nil {
return nil, err
}
if tee == nil {
tee = &TeeEntry{
Name: name,
}
}
// Get non tokenutil fields
if displayNameRaw, ok := d.GetOk("display_name"); ok {
tee.DisplayName = displayNameRaw.(string)
}
if teeTypes, ok := d.GetOk("types"); ok {
tee.Types = make(map[string]bool)
handled := make(map[string]bool)
for _, t := range teeTypes.([]string) {
// only SGX supported for now
if _, ok = handled[t]; ok {
return logical.ErrorResponse(fmt.Sprintf("duplicate TEE type `%s`", t)), nil
}
if t == "sgx" {
tee.Types[t] = true
handled[t] = true
response, err := handleSGXConfig(d, tee)
if response != nil || err != nil {
return response, err
}
} else {
return logical.ErrorResponse(fmt.Sprintf("invalid TEE type `%s`", t)), nil
}
}
} else {
return logical.ErrorResponse("missing TEE types"), nil
}
// Get tokenutil fields
if err := tee.ParseTokenFields(req, d); err != nil {
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
}
var resp logical.Response
systemDefaultTTL := b.System().DefaultLeaseTTL()
if tee.TokenTTL > systemDefaultTTL {
resp.AddWarning(fmt.Sprintf("Given ttl of %d seconds is greater than current mount/system default of %d seconds", tee.TokenTTL/time.Second, systemDefaultTTL/time.Second))
}
systemMaxTTL := b.System().MaxLeaseTTL()
if tee.TokenMaxTTL > systemMaxTTL {
resp.AddWarning(fmt.Sprintf("Given max_ttl of %d seconds is greater than current mount/system default of %d seconds", tee.TokenMaxTTL/time.Second, systemMaxTTL/time.Second))
}
if tee.TokenMaxTTL != 0 && tee.TokenTTL > tee.TokenMaxTTL {
return logical.ErrorResponse("ttl should be shorter than max_ttl"), nil
}
if tee.TokenPeriod > systemMaxTTL {
resp.AddWarning(fmt.Sprintf("Given period of %d seconds is greater than the backend's maximum TTL of %d seconds", tee.TokenPeriod/time.Second, systemMaxTTL/time.Second))
}
// Default the display name to the certificate name if not given
if tee.DisplayName == "" {
tee.DisplayName = name
}
// Store it
entry, err := logical.StorageEntryJSON("tee/"+name, tee)
if err != nil {
return nil, err
}
if err := req.Storage.Put(ctx, entry); err != nil {
return nil, err
}
if len(resp.Warnings) == 0 {
return nil, nil
}
return &resp, nil
}
func handleSGXConfig(d *framework.FieldData, tee *TeeEntry) (*logical.Response, error) {
if sgxMrsignerRaw, ok := d.GetOk("sgx_mrsigner"); ok && sgxMrsignerRaw.(string) != "" {
tee.SgxMrsigner = strings.ToLower(sgxMrsignerRaw.(string))
b, err := hex.DecodeString(tee.SgxMrsigner)
if err != nil || len(b) != 32 {
return logical.ErrorResponse("`sgx_mrsigner` must be 32 byte hex encoded"), nil
}
}
if sgxMrenclaveRaw, ok := d.GetOk("sgx_mrenclave"); ok && sgxMrenclaveRaw.(string) != "" {
tee.SgxMrenclave = strings.ToLower(sgxMrenclaveRaw.(string))
b, err := hex.DecodeString(tee.SgxMrenclave)
if err != nil || len(b) != 32 {
return logical.ErrorResponse("`sgx_mrenclave` must be 32 byte hex encoded"), nil
}
}
if tee.SgxMrsigner == "" && tee.SgxMrenclave == "" {
return logical.ErrorResponse("either `sgx_mrsigner` or `sgx_mrenclave` must be set"), nil
}
if sgxIsvProdidRaw, ok := d.GetOk("sgx_isv_prodid"); ok {
tee.SgxIsvProdid = sgxIsvProdidRaw.(int)
}
if sgxMinIsvSvnRaw, ok := d.GetOk("sgx_min_isv_svn"); ok {
tee.SgxMinIsvSvn = sgxMinIsvSvnRaw.(int)
}
if sgxAllowedTcbLevelsRaw, ok := d.GetOk("sgx_allowed_tcb_levels"); ok {
tee.SgxAllowedTcbLevels = make(map[ratee.SgxQlQvResult]bool)
for _, v := range sgxAllowedTcbLevelsRaw.([]string) {
var state ratee.SgxQlQvResult
switch v {
case "Ok":
state = ratee.SgxQlQvResultOk
case "ConfigNeeded":
state = ratee.SgxQlQvResultConfigNeeded
case "OutOfDate":
state = ratee.SgxQlQvResultOutOfDate
case "OutOfDateConfigNeeded":
state = ratee.SgxQlQvResultOutOfDateConfigNeeded
case "SwHardeningNeeded":
state = ratee.SgxQlQvResultSwHardeningNeeded
case "ConfigAndSwHardeningNeeded":
state = ratee.SgxQlQvResultConfigAndSwHardeningNeeded
default:
return logical.ErrorResponse("invalid sgx_allowed_tcb_levels value"), logical.ErrInvalidRequest
}
tee.SgxAllowedTcbLevels[state] = true
}
}
return nil, nil
}
type TeeEntry struct {
tokenutil.TokenParams
Name string
DisplayName string
Types map[string]bool
SgxMrsigner string
SgxMrenclave string
SgxIsvProdid int
SgxMinIsvSvn int
SgxAllowedTcbLevels map[ratee.SgxQlQvResult]bool
}
const pathTeeHelpSyn = `
Manage TEE remote attestation parameters used for authentication.`
const pathTeeHelpDesc = `
This endpoint allows you to create, read, update, and delete TEEs
that are allowed to authenticate.
Deleting a TEE will not revoke auth for prior authenticated connections.
To do this, do a revoke on "login". If you don't need to revoke login immediately,
then the next renew will cause the lease to expire.
`

View file

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIUf+jhKTFBnqSs34II0WS1L4QsbbAwDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMjI5MDIyNzQxWhcNMjUw
MTA1MTAyODExWjAbMRkwFwYDVQQDExBjZXJ0LmV4YW1wbGUuY29tMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsZx0Svr82YJpFpIy4fJNW5fKA6B8mhxS
TRAVnygAftetT8puHflY0ss7Y6X2OXjsU0PRn+1PswtivhKi+eLtgWkUF9cFYFGn
SgMld6ZWRhNheZhA6ZfQmeM/BF2pa5HK2SDF36ljgjL9T+nWrru2Uv0BCoHzLAmi
YYMiIWplidMmMO5NTRG3k+3AN0TkfakB6JVzjLGhTcXdOcVEMXkeQVqJMAuGouU5
donyqtnaHuIJGuUdy54YDnX86txhOQhAv6r7dHXzZxS4pmLvw8UI1rsSf/GLcUVG
B+5+AAGF5iuHC3N2DTl4xz3FcN4Cb4w9pbaQ7+mCzz+anqiJfyr2nwIDAQABo4H1
MIHyMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUm++e
HpyM3p708bgZJuRYEdX1o+UwHwYDVR0jBBgwFoAUncSzT/6HMexyuiU9/7EgHu+o
k5swOwYIKwYBBQUHAQEELzAtMCsGCCsGAQUFBzAChh9odHRwOi8vMTI3LjAuMC4x
OjgyMDAvdjEvcGtpL2NhMCEGA1UdEQQaMBiCEGNlcnQuZXhhbXBsZS5jb22HBH8A
AAEwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDovLzEyNy4wLjAuMTo4MjAwL3YxL3Br
aS9jcmwwDQYJKoZIhvcNAQELBQADggEBABsuvmPSNjjKTVN6itWzdQy+SgMIrwfs
X1Yb9Lefkkwmp9ovKFNQxa4DucuCuzXcQrbKwWTfHGgR8ct4rf30xCRoA7dbQWq4
aYqNKFWrRaBRAaaYZ/O1ApRTOrXqRx9Eqr0H1BXLsoAq+mWassL8sf6siae+CpwA
KqBko5G0dNXq5T4i2LQbmoQSVetIrCJEeMrU+idkuqfV2h1BQKgSEhFDABjFdTCN
QDAHsEHsi2M4/jRW9fqEuhHSDfl2n7tkFUI8wTHUUCl7gXwweJ4qtaSXIwKXYzNj
xqKHA8Purc1Yfybz4iE1JCROi9fInKlzr5xABq8nb9Qc/J9DIQM+Xmk=
-----END CERTIFICATE-----

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAsZx0Svr82YJpFpIy4fJNW5fKA6B8mhxSTRAVnygAftetT8pu
HflY0ss7Y6X2OXjsU0PRn+1PswtivhKi+eLtgWkUF9cFYFGnSgMld6ZWRhNheZhA
6ZfQmeM/BF2pa5HK2SDF36ljgjL9T+nWrru2Uv0BCoHzLAmiYYMiIWplidMmMO5N
TRG3k+3AN0TkfakB6JVzjLGhTcXdOcVEMXkeQVqJMAuGouU5donyqtnaHuIJGuUd
y54YDnX86txhOQhAv6r7dHXzZxS4pmLvw8UI1rsSf/GLcUVGB+5+AAGF5iuHC3N2
DTl4xz3FcN4Cb4w9pbaQ7+mCzz+anqiJfyr2nwIDAQABAoIBAHR7fFV0eAGaopsX
9OD0TUGlsephBXb43g0GYHfJ/1Ew18w9oaxszJEqkl+PB4W3xZ3yG3e8ZomxDOhF
RreF2WgG5xOfhDogMwu6NodbArfgnAvoC6JnW3qha8HMP4F500RFVyCRcd6A3Frd
rFtaZn/UyCsBAN8/zkwPeYHayo7xX6d9kzgRl9HluEX5PXI5+3uiBDUiM085gkLI
5Cmadh9fMdjfhDXI4x2JYmILpp/9Nlc/krB15s5n1MPNtn3yL0TI0tWp0WlwDCV7
oUm1SfIM0F1fXGFyFDcqwoIr6JCQgXk6XtTg31YhH1xgUIclUVdtHqmAwAbLdIhQ
GAiHn2kCgYEAwD4pZ8HfpiOG/EHNoWsMATc/5yC7O8F9WbvcHZQIymLY4v/7HKZb
VyOR6UQ5/O2cztSGIuKSF6+OK1C34lOyCuTSOTFrjlgEYtLIXjdGLfFdtOO8GRQR
akVXdwuzNAjTBaH5eXbG+NKcjmCvZL48dQVlfDTVulzFGbcsVTHIMQUCgYEA7IQI
FVsKnY3KqpyGqXq92LMcsT3XgW6X1BIIV+YhJ5AFUFkFrjrbXs94/8XyLfi0xBQy
efK+8g5sMs7koF8LyZEcAXWZJQduaKB71hoLlRaU4VQkL/dl2B6VFmAII/CsRCYh
r9RmDN2PF/mp98Ih9dpC1VqcCDRGoTYsd7jLalMCgYAMgH5k1wDaZxkSMp1S0AlZ
0uP+/evvOOgT+9mWutfPgZolOQx1koQCKLgGeX9j6Xf3I28NubpSfAI84uTyfQrp
FnRtb79U5Hh0jMynA+U2e6niZ6UF5H41cQj9Hu+qhKBkj2IP+h96cwfnYnZFkPGR
kqZE65KyqfHPeFATwkcImQKBgCdrfhlpGiTWXCABhKQ8s+WpPLAB2ahV8XJEKyXT
UlVQuMIChGLcpnFv7P/cUxf8asx/fUY8Aj0/0CLLvulHziQjTmKj4gl86pb/oIQ3
xRRtNhU0O+/OsSfLORgIm3K6C0w0esregL/GMbJSR1TnA1gBr7/1oSnw5JC8Ab9W
injHAoGAJT1MGAiQrhlt9GCGe6Ajw4omdbY0wS9NXefnFhf7EwL0es52ezZ28zpU
2LXqSFbtann5CHgpSLxiMYPDIf+er4xgg9Bz34tz1if1rDfP2Qrxdrpr4jDnrGT3
gYC2qCpvVD9RRUMKFfnJTfl5gMQdBW/LINkHtJ82snAeLl3gjQ4=
-----END RSA PRIVATE KEY-----

302
tee/test_responder.go Normal file
View file

@ -0,0 +1,302 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) HashiCorp, Inc.
// Package ocsp implements an OCSP responder based on a generic storage backend.
// It provides a couple of sample implementations.
// Because OCSP responders handle high query volumes, we have to be careful
// about how much logging we do. Error-level logs are reserved for problems
// internal to the server, that can be fixed by an administrator. Any type of
// incorrect input from a user should be logged and Info or below. For things
// that are logged on every request, Debug is the appropriate level.
//
// From https://github.com/cloudflare/cfssl/blob/master/ocsp/responder.go
package tee
import (
"crypto"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
"golang.org/x/crypto/ocsp"
)
var (
malformedRequestErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x01}
internalErrorErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x02}
unauthorizedErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x06}
// ErrNotFound indicates the request OCSP response was not found. It is used to
// indicate that the responder should reply with unauthorizedErrorResponse.
ErrNotFound = errors.New("Request OCSP Response not found")
)
// Source represents the logical source of OCSP responses, i.e.,
// the logic that actually chooses a response based on a request. In
// order to create an actual responder, wrap one of these in a Responder
// object and pass it to http.Handle. By default the Responder will set
// the headers Cache-Control to "max-age=(response.NextUpdate-now), public, no-transform, must-revalidate",
// Last-Modified to response.ThisUpdate, Expires to response.NextUpdate,
// ETag to the SHA256 hash of the response, and Content-Type to
// application/ocsp-response. If you want to override these headers,
// or set extra headers, your source should return a http.Header
// with the headers you wish to set. If you don'log want to set any
// extra headers you may return nil instead.
type Source interface {
Response(*ocsp.Request) ([]byte, http.Header, error)
}
// An InMemorySource is a map from serialNumber -> der(response)
type InMemorySource map[string][]byte
// Response looks up an OCSP response to provide for a given request.
// InMemorySource looks up a response purely based on serial number,
// without regard to what issuer the request is asking for.
func (src InMemorySource) Response(request *ocsp.Request) ([]byte, http.Header, error) {
response, present := src[request.SerialNumber.String()]
if !present {
return nil, nil, ErrNotFound
}
return response, nil, nil
}
// Stats is a basic interface that allows users to record information
// about returned responses
type Stats interface {
ResponseStatus(ocsp.ResponseStatus)
}
type logger interface {
Log(args ...any)
}
// A Responder object provides the HTTP logic to expose a
// Source of OCSP responses.
type Responder struct {
log logger
Source Source
stats Stats
}
// NewResponder instantiates a Responder with the give Source.
func NewResponder(t logger, source Source, stats Stats) *Responder {
return &Responder{
Source: source,
stats: stats,
log: t,
}
}
func overrideHeaders(response http.ResponseWriter, headers http.Header) {
for k, v := range headers {
if len(v) == 1 {
response.Header().Set(k, v[0])
} else if len(v) > 1 {
response.Header().Del(k)
for _, e := range v {
response.Header().Add(k, e)
}
}
}
}
// hashToString contains mappings for the only hash functions
// x/crypto/ocsp supports
var hashToString = map[crypto.Hash]string{
crypto.SHA1: "SHA1",
crypto.SHA256: "SHA256",
crypto.SHA384: "SHA384",
crypto.SHA512: "SHA512",
}
// A Responder can process both GET and POST requests. The mapping
// from an OCSP request to an OCSP response is done by the Source;
// the Responder simply decodes the request, and passes back whatever
// response is provided by the source.
// Note: The caller must use http.StripPrefix to strip any path components
// (including '/') on GET requests.
// Do not use this responder in conjunction with http.NewServeMux, because the
// default handler will try to canonicalize path components by changing any
// strings of repeated '/' into a single '/', which will break the base64
// encoding.
func (rs *Responder) ServeHTTP(response http.ResponseWriter, request *http.Request) {
// By default we set a 'max-age=0, no-cache' Cache-Control header, this
// is only returned to the client if a valid authorized OCSP response
// is not found or an error is returned. If a response if found the header
// will be altered to contain the proper max-age and modifiers.
response.Header().Add("Cache-Control", "max-age=0, no-cache")
// Read response from request
var requestBody []byte
var err error
switch request.Method {
case "GET":
base64Request, err := url.QueryUnescape(request.URL.Path)
if err != nil {
rs.log.Log("Error decoding URL:", request.URL.Path)
response.WriteHeader(http.StatusBadRequest)
return
}
// url.QueryUnescape not only unescapes %2B escaping, but it additionally
// turns the resulting '+' into a space, which makes base64 decoding fail.
// So we go back afterwards and turn ' ' back into '+'. This means we
// accept some malformed input that includes ' ' or %20, but that's fine.
base64RequestBytes := []byte(base64Request)
for i := range base64RequestBytes {
if base64RequestBytes[i] == ' ' {
base64RequestBytes[i] = '+'
}
}
// In certain situations a UA may construct a request that has a double
// slash between the host name and the base64 request body due to naively
// constructing the request URL. In that case strip the leading slash
// so that we can still decode the request.
if len(base64RequestBytes) > 0 && base64RequestBytes[0] == '/' {
base64RequestBytes = base64RequestBytes[1:]
}
requestBody, err = base64.StdEncoding.DecodeString(string(base64RequestBytes))
if err != nil {
rs.log.Log("Error decoding base64 from URL", string(base64RequestBytes))
response.WriteHeader(http.StatusBadRequest)
return
}
case "POST":
requestBody, err = ioutil.ReadAll(request.Body)
if err != nil {
rs.log.Log("Problem reading body of POST", err)
response.WriteHeader(http.StatusBadRequest)
return
}
default:
response.WriteHeader(http.StatusMethodNotAllowed)
return
}
b64Body := base64.StdEncoding.EncodeToString(requestBody)
rs.log.Log("Received OCSP request", b64Body)
// All responses after this point will be OCSP.
// We could check for the content type of the request, but that
// seems unnecessariliy restrictive.
response.Header().Add("Content-Type", "application/ocsp-response")
// Parse response as an OCSP request
// XXX: This fails if the request contains the nonce extension.
// We don'log intend to support nonces anyway, but maybe we
// should return unauthorizedRequest instead of malformed.
ocspRequest, err := ocsp.ParseRequest(requestBody)
if err != nil {
rs.log.Log("Error decoding request body", b64Body)
response.WriteHeader(http.StatusBadRequest)
response.Write(malformedRequestErrorResponse)
if rs.stats != nil {
rs.stats.ResponseStatus(ocsp.Malformed)
}
return
}
// Look up OCSP response from source
ocspResponse, headers, err := rs.Source.Response(ocspRequest)
if err != nil {
if err == ErrNotFound {
rs.log.Log("No response found for request: serial %x, request body %s",
ocspRequest.SerialNumber, b64Body)
response.Write(unauthorizedErrorResponse)
if rs.stats != nil {
rs.stats.ResponseStatus(ocsp.Unauthorized)
}
return
}
rs.log.Log("Error retrieving response for request: serial %x, request body %s, error",
ocspRequest.SerialNumber, b64Body, err)
response.WriteHeader(http.StatusInternalServerError)
response.Write(internalErrorErrorResponse)
if rs.stats != nil {
rs.stats.ResponseStatus(ocsp.InternalError)
}
return
}
parsedResponse, err := ocsp.ParseResponse(ocspResponse, nil)
if err != nil {
rs.log.Log("Error parsing response for serial %x",
ocspRequest.SerialNumber, err)
response.Write(internalErrorErrorResponse)
if rs.stats != nil {
rs.stats.ResponseStatus(ocsp.InternalError)
}
return
}
// Write OCSP response to response
response.Header().Add("Last-Modified", parsedResponse.ThisUpdate.Format(time.RFC1123))
response.Header().Add("Expires", parsedResponse.NextUpdate.Format(time.RFC1123))
now := time.Now()
maxAge := 0
if now.Before(parsedResponse.NextUpdate) {
maxAge = int(parsedResponse.NextUpdate.Sub(now) / time.Second)
} else {
// TODO(#530): we want max-age=0 but this is technically an authorized OCSP response
// (despite being stale) and 5019 forbids attaching no-cache
maxAge = 0
}
response.Header().Set(
"Cache-Control",
fmt.Sprintf(
"max-age=%d, public, no-transform, must-revalidate",
maxAge,
),
)
responseHash := sha256.Sum256(ocspResponse)
response.Header().Add("ETag", fmt.Sprintf("\"%X\"", responseHash))
if headers != nil {
overrideHeaders(response, headers)
}
// RFC 7232 says that a 304 response must contain the above
// headers if they would also be sent for a 200 for the same
// request, so we have to wait until here to do this
if etag := request.Header.Get("If-None-Match"); etag != "" {
if etag == fmt.Sprintf("\"%X\"", responseHash) {
response.WriteHeader(http.StatusNotModified)
return
}
}
response.WriteHeader(http.StatusOK)
response.Write(ocspResponse)
if rs.stats != nil {
rs.stats.ResponseStatus(ocsp.Success)
}
}
/*
Copyright (c) 2014 CloudFlare Inc.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

7
version/version.go Normal file
View file

@ -0,0 +1,7 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Matter Labs
package version
const Version = "0.1.0+dev"
const Name = "vault-auth-tee"