Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add /.well-known/transparency-configuration and /jwks endpoints #253

Merged
merged 21 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
FROM ghcr.io/microsoft/ccf/app/dev/virtual:ccf-6.0.0-dev8
FROM ghcr.io/microsoft/ccf/app/dev/virtual:ccf-6.0.0-dev10
4 changes: 2 additions & 2 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
checks:
name: Format and License Checks
runs-on: ubuntu-20.04
container: ghcr.io/microsoft/ccf/app/dev/virtual:ccf-6.0.0-dev8
container: ghcr.io/microsoft/ccf/app/dev/virtual:ccf-6.0.0-dev10
steps:
- run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Checkout repository
Expand All @@ -37,7 +37,7 @@ jobs:
unit_tests_enabled: OFF
runs-on: ${{ matrix.platform.nodes }}
container:
image: ghcr.io/microsoft/ccf/app/dev/${{ matrix.platform.image }}:ccf-6.0.0-dev8
image: ghcr.io/microsoft/ccf/app/dev/${{ matrix.platform.image }}:ccf-6.0.0-dev10
options: ${{ matrix.platform.options }}
env:
# Helps to distinguish between CI and local builds.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
name: Analyze

runs-on: ubuntu-latest
container: ghcr.io/microsoft/ccf/app/dev/virtual:ccf-6.0.0-dev8
container: ghcr.io/microsoft/ccf/app/dev/virtual:ccf-6.0.0-dev10

permissions:
actions: read
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/long-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
name: fuzz
if: ${{ contains(github.event.pull_request.labels.*.name, 'run-fuzz-test') || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' }}
runs-on: ubuntu-20.04
container: ghcr.io/microsoft/ccf/app/dev/virtual:ccf-6.0.0-dev8
container: ghcr.io/microsoft/ccf/app/dev/virtual:ccf-6.0.0-dev10
env:
PLATFORM: virtual
steps:
Expand Down
2 changes: 1 addition & 1 deletion .pipelines/pullrequest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ parameters: # parameters are shown up in ADO UI in a build queue time
- name: CCF_VERSION
displayName: Target CCF version to build for
type: string
default: 6.0.0-dev8
default: 6.0.0-dev10

variables:
SCITT_CI: 1 # used in scitt builds and tests
Expand Down
8 changes: 4 additions & 4 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ It is expected that you have Ubuntu 20.04. Follow the steps below to setup your

2. Install dependencies:
```sh
wget https://github.com/microsoft/CCF/archive/refs/tags/ccf-6.0.0-dev8.tar.gz
tar xvzf ccf-6.0.0-dev8.tar.gz
cd CCF-ccf-6.0.0-dev8/getting_started/setup_vm/
./run.sh app-dev.yml -e ccf_ver=6.0.0-dev8 -e platform=<virtual|snp> -e clang_version=15
wget https://github.com/microsoft/CCF/archive/refs/tags/ccf-6.0.0-dev10.tar.gz
tar xvzf ccf-6.0.0-dev10.tar.gz
cd CCF-ccf-6.0.0-dev10/getting_started/setup_vm/
./run.sh app-dev.yml -e ccf_ver=6.0.0-dev10 -e platform=<virtual|snp> -e clang_version=15
```

## Compiling
Expand Down
154 changes: 152 additions & 2 deletions app/src/service_endpoints.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
#include "visit_each_entry_in_value.h"

#include <ccf/base_endpoint_registry.h>
#include <ccf/cose_signatures_config_interface.h>
#include <ccf/crypto/verifier.h>
#include <ccf/endpoint.h>
#include <ccf/http_accept.h>
#include <ccf/json_handler.h>
#include <ccf/service/tables/service.h>

Expand Down Expand Up @@ -49,9 +51,55 @@ namespace scitt
}

/**
* An indexing strategy collecting all past and present service certificates
* and makes them immediately available.
* An indexing strategy collecting service keys used to sign receipts.
*/
class ServiceKeyIndexingStrategy
: public VisitEachEntryInValueTyped<ccf::Service>
{
public:
ServiceKeyIndexingStrategy() :
VisitEachEntryInValueTyped(ccf::Tables::SERVICE)
{}

nlohmann::json get_jwks() const
{
std::lock_guard guard(lock);

std::vector<nlohmann::json> jwks;
for (const auto& service_certificate : service_certificates)
{
auto verifier = ccf::crypto::make_unique_verifier(service_certificate);
auto kid =
ccf::crypto::Sha256Hash(verifier->public_key_der()).hex_str();
nlohmann::json json_jwk = verifier->public_key_jwk();
json_jwk["kid"] = kid;
jwks.emplace_back(std::move(json_jwk));
}
nlohmann::json jwks_json;
jwks_json["keys"] = jwks;
return jwks_json;
}

protected:
void visit_entry(
const ccf::TxID& tx_id, const ccf::ServiceInfo& service_info) override
{
std::lock_guard guard(lock);

// It is possible for multiple entries in the ServiceInfo table to contain
// the same certificate, eg. if the service status changes. Using an
// std::set removes duplicates.
service_certificates.insert(service_info.cert);
}

private:
mutable std::mutex lock;

std::set<ccf::crypto::Pem> service_certificates;
}; /**
* An indexing strategy collecting all past and present service
* certificates and makes them immediately available.
*/
class ServiceCertificateIndexingStrategy
: public VisitEachEntryInValueTyped<ccf::Service>
{
Expand Down Expand Up @@ -160,6 +208,16 @@ namespace scitt

return index->get_did_document(*cfg.service_identifier);
}

static nlohmann::json get_jwks(
const std::shared_ptr<ServiceKeyIndexingStrategy>& index,
ccf::endpoints::EndpointContext& ctx,
nlohmann::json&& params)
{
// Like get_did_document(), this is not right when the indexer is not up
// to date, which needs fixing
return index->get_jwks();
}
}

static void register_service_endpoints(
Expand All @@ -172,9 +230,84 @@ namespace scitt
auto service_certificate_index =
std::make_shared<ServiceCertificateIndexingStrategy>();

auto service_key_index = std::make_shared<ServiceKeyIndexingStrategy>();

context.get_indexing_strategies().install_strategy(
service_certificate_index);

context.get_indexing_strategies().install_strategy(service_key_index);

auto get_transparency_config =
[&](ccf::endpoints::ReadOnlyEndpointContext& ctx) {
auto subsystem =
context.get_subsystem<ccf::cose::AbstractCOSESignaturesConfig>();
if (!subsystem)
{
ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"COSE signatures subsystem not available");
return;
}
auto cfg = subsystem->get_cose_signatures_config();

nlohmann::json config;
config["issuer"] = cfg.issuer;
config["jwksUri"] = fmt::format("https://{}/jwks", cfg.issuer);

const auto accept =
ctx.rpc_ctx->get_request_header(ccf::http::headers::ACCEPT);
if (accept.has_value())
{
const auto accept_options =
ccf::http::parse_accept_header(accept.value());
if (accept_options.empty())
{
throw ccf::RpcException(
HTTP_STATUS_NOT_ACCEPTABLE,
ccf::errors::UnsupportedContentType,
fmt::format(
"No supported content type in accept header: {}\nOnly {} is "
"currently supported",
accept.value(),
ccf::http::headervalues::contenttype::JSON));
achamayou marked this conversation as resolved.
Show resolved Hide resolved
}

for (const auto& option : accept_options)
{
// return CBOR eagerly if it is compatible with Accept
if (option.matches(ccf::http::headervalues::contenttype::CBOR))
{
ctx.rpc_ctx->set_response_status(HTTP_STATUS_OK);
ctx.rpc_ctx->set_response_header(
ccf::http::headers::CONTENT_TYPE,
ccf::http::headervalues::contenttype::CBOR);
ctx.rpc_ctx->set_response_body(nlohmann::json::to_cbor(config));
return;
}

// JSON if compatible with Accept
if (option.matches(ccf::http::headervalues::contenttype::JSON))
{
ctx.rpc_ctx->set_response_status(HTTP_STATUS_OK);
ctx.rpc_ctx->set_response_header(
ccf::http::headers::CONTENT_TYPE,
ccf::http::headervalues::contenttype::JSON);
ctx.rpc_ctx->set_response_body(config.dump());
return;
}
}
}

// If not Accept, default to CBOR
ctx.rpc_ctx->set_response_status(HTTP_STATUS_OK);
ctx.rpc_ctx->set_response_header(
ccf::http::headers::CONTENT_TYPE,
ccf::http::headervalues::contenttype::CBOR);
ctx.rpc_ctx->set_response_body(nlohmann::json::to_cbor(config));
return;
};

registry
.make_endpoint(
"/parameters",
Expand Down Expand Up @@ -237,5 +370,22 @@ namespace scitt
endpoints::get_did_document, service_certificate_index, _1, _2)),
{ccf::empty_auth_policy})
.install();

registry
.make_endpoint(
"/jwks",
HTTP_GET,
ccf::json_adapter(
std::bind(endpoints::get_jwks, service_key_index, _1, _2)),
{ccf::empty_auth_policy})
.install();

registry
.make_read_only_endpoint(
"/.well-known/transparency-configuration",
HTTP_GET,
get_transparency_config,
{ccf::empty_auth_policy})
.install();
}
}
2 changes: 1 addition & 1 deletion build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ if [ "$PLATFORM" != "virtual" ] && [ "$PLATFORM" != "snp" ]; then
fi

if [ "$BUILD_CCF_FROM_SOURCE" = "ON" ]; then
CCF_SOURCE_VERSION="6.0.0-dev8"
CCF_SOURCE_VERSION="6.0.0-dev10"
echo "Cloning CCF sources"
rm -rf ccf-source
git clone --single-branch -b "ccf-${CCF_SOURCE_VERSION}" https://github.com/microsoft/CCF ccf-source
Expand Down
2 changes: 1 addition & 1 deletion docker/snp.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ARG CCF_VERSION=6.0.0-dev8
ARG CCF_VERSION=6.0.0-dev10
FROM ghcr.io/microsoft/ccf/app/dev/snp:ccf-${CCF_VERSION} as builder
ARG CCF_VERSION
ARG SCITT_VERSION_OVERRIDE
Expand Down
2 changes: 1 addition & 1 deletion docker/virtual.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ARG CCF_VERSION=6.0.0-dev8
ARG CCF_VERSION=6.0.0-dev10
FROM ghcr.io/microsoft/ccf/app/dev/virtual:ccf-${CCF_VERSION} as builder
ARG CCF_VERSION
ARG SCITT_VERSION_OVERRIDE
Expand Down
4 changes: 2 additions & 2 deletions pyscitt/pyscitt/cli/retrieve_signed_claims.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import Optional

from ..client import Client
from ..verify import StaticTrustStore, verify_receipt
from ..verify import StaticTrustStore, verify_transparent_statement
from .client_arguments import add_client_arguments, create_client


Expand All @@ -29,7 +29,7 @@ def retrieve_signed_claimsets(
path = base_path / f"{tx}.cose"

if service_trust_store:
verify_receipt(claim, service_trust_store=service_trust_store)
verify_transparent_statement(claim, service_trust_store, claim)

with open(path, "wb") as f:
f.write(claim)
Expand Down
3 changes: 3 additions & 0 deletions pyscitt/pyscitt/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,9 @@ def get_did_document(self, did: str) -> dict:
# Note: This endpoint only returns data for did:web DIDs.
return self.get(f"/did/{did}").json()["did_document"]

def get_jwks(self) -> dict:
return self.get(f"/jwks").json()

def submit_signed_statement(
self,
signed_statement: bytes,
Expand Down
Loading
Loading