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

Allow svn parameter in the protected header #209

Merged
merged 1 commit into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 12 additions & 0 deletions app/src/cose.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ namespace scitt::cose
static constexpr const char* NOTARY_HEADER_PARAM_EXPIRY =
"io.cncf.notary.expiry";

static constexpr const char* SVN_HEADER_PARAM = "svn";

static const std::set<std::variant<int64_t, std::string>>
NOTARY_HEADER_PARAMS{
NOTARY_HEADER_PARAM_SIGNING_SCHEME,
Expand All @@ -89,6 +91,7 @@ namespace scitt::cose
std::optional<std::string> kid;
std::optional<std::string> issuer;
std::optional<std::string> feed;
std::optional<int64_t> svn;
std::optional<std::variant<int64_t, std::string>> cty;
std::optional<std::vector<std::vector<uint8_t>>> x5chain;

Expand Down Expand Up @@ -296,6 +299,7 @@ namespace scitt::cose
CRIT_INDEX,
ISSUER_INDEX,
FEED_INDEX,
SVN_INDEX,
KID_INDEX,
CTY_INDEX,
X5CHAIN_INDEX,
Expand Down Expand Up @@ -323,6 +327,10 @@ namespace scitt::cose
header_items[FEED_INDEX].uLabelType = QCBOR_TYPE_INT64;
header_items[FEED_INDEX].uDataType = QCBOR_TYPE_TEXT_STRING;

header_items[SVN_INDEX].label.string = UsefulBuf_FromSZ(SVN_HEADER_PARAM);
header_items[SVN_INDEX].uLabelType = QCBOR_TYPE_TEXT_STRING;
header_items[SVN_INDEX].uDataType = QCBOR_TYPE_INT64;

header_items[KID_INDEX].label.int64 = COSE_HEADER_PARAM_KID;
header_items[KID_INDEX].uLabelType = QCBOR_TYPE_INT64;
header_items[KID_INDEX].uDataType = QCBOR_TYPE_BYTE_STRING;
Expand Down Expand Up @@ -425,6 +433,10 @@ namespace scitt::cose
{
parsed.feed = cbor::as_string(header_items[FEED_INDEX].val.string);
}
if (header_items[SVN_INDEX].uDataType != QCBOR_TYPE_NONE)
{
parsed.svn = header_items[SVN_INDEX].val.int64;
}

if (header_items[CTY_INDEX].uDataType == QCBOR_TYPE_TEXT_STRING)
{
Expand Down
5 changes: 5 additions & 0 deletions app/src/policy_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ namespace scitt
obj.set("feed", ctx.new_string(phdr.feed.value()));
}

if (phdr.svn.has_value())
{
obj.set_int64("svn", phdr.svn.value());
}

if (phdr.cty.has_value())
{
if (std::holds_alternative<int64_t>(phdr.cty.value()))
Expand Down
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ JS code that determines whether an entry should be accepted. Should export an `a
- kid (String)
- issuer (String)
- feed (String)
- svn (Number)
- cty (Number or String)
- x5chain (Array of String values)
- notary_signing_scheme (String)
Expand Down
10 changes: 9 additions & 1 deletion pyscitt/pyscitt/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -685,10 +685,13 @@ def sign_claimset(
content_type: str,
feed: Optional[str] = None,
registration_info: RegistrationInfo = {},
svn: Optional[int] = None,
) -> bytes:
headers: dict = {}
headers[pycose.headers.Algorithm] = signer.algorithm
headers[pycose.headers.ContentType] = content_type
if svn is not None:
headers["svn"] = svn

if signer.x5c is not None:
headers[pycose.headers.X5chain] = [cert_pem_to_der(x5) for x5 in signer.x5c]
Expand All @@ -711,9 +714,14 @@ def sign_json_claimset(
claims: Any,
content_type: str = "application/vnd.dummy+json",
feed: Optional[str] = None,
svn: Optional[int] = None,
) -> bytes:
return sign_claimset(
signer, json.dumps(claims).encode("ascii"), content_type=content_type, feed=feed
signer,
json.dumps(claims).encode("ascii"),
content_type=content_type,
feed=feed,
svn=svn,
)


Expand Down
64 changes: 64 additions & 0 deletions test/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,70 @@ def didx509_issuer(ca):
// Check exact issuer
if (phdr.issuer !== "{didx509_issuer(trusted_ca)}") {{ return "Invalid issuer"; }}

return true;
}}"""

configure_service({"policy": {"policy_script": policy_script}})

for signed_claimset in permitted_signed_claims:
client.submit_claim(signed_claimset)

for err, signed_claimsets in refused_signed_claims.items():
for signed_claimset in signed_claimsets:
with service_error(err):
client.submit_claim(signed_claimset)

def test_svn_policy(
self,
client: Client,
configure_service,
trusted_ca: X5ChainCertificateAuthority,
did_web,
):
example_eku = "2.999"

identity = trusted_ca.create_identity(
alg="ES256", kty="ec", add_eku=example_eku
)

def didx509_issuer(ca):
root_cert = ca.cert_bundle
root_fingerprint = crypto.get_cert_fingerprint_b64url(root_cert)
return f"did:x509:0:sha256:{root_fingerprint}::eku:{example_eku}"

identity.issuer = didx509_issuer(trusted_ca)
feed = "SomeFeed"
# SBOMs
claims = {"foo": "bar"}

permitted_signed_claims = [
crypto.sign_json_claimset(identity, claims, feed=feed, svn=1),
]

profile_error = "This policy only accepts IETF did:x509 claims"
invalid_svn = "Invalid SVN"

# Keyed by expected error, values are lists of claimsets which should trigger this error
refused_signed_claims = {
# Well-constructed, but not a valid issuer
invalid_svn: [
crypto.sign_json_claimset(
identity,
claims,
feed=feed,
),
crypto.sign_json_claimset(identity, claims, feed=feed, svn=-11),
],
}

policy_script = f"""
export function apply(profile, phdr) {{
if (profile !== "IETF") {{ return "{profile_error}"; }}

// Check exact issuer
if (phdr.issuer !== "{didx509_issuer(trusted_ca)}") {{ return "Invalid issuer"; }}
if (phdr.svn === undefined || phdr.svn < 0) {{ return "Invalid SVN"; }}

return true;
}}"""

Expand Down
Loading