Skip to content

Ietf status list#108

Open
wistefan wants to merge 2 commits into
mainfrom
ietf-status-list
Open

Ietf status list#108
wistefan wants to merge 2 commits into
mainfrom
ietf-status-list

Conversation

@wistefan

Copy link
Copy Markdown
Collaborator

@wistefan wistefan added the minor Should be applied for new functionality or bigger updates. label Jun 26, 2026
@wistefan wistefan requested a review from Mortega5 June 26, 2026 07:53
// References:
// - https://www.w3.org/TR/vc-bitstring-status-list/
// - https://www.w3.org/TR/2023/WD-vc-status-list-20230427/ (StatusList2021)
// - https://www.ietf.org/archive/id/draft-ietf-oauth-status-list-11.html

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed a link to OAuth Status List 11, but it expired a year ago. The latest version is 21. Is there any reason to use version 11 instead of 21? There are no significant changes, but I think we should reference version 21 instead.

// parseIETFStatusListJWT extracts the `status_list` payload from an IETF
// Token Status List JWT without verifying the signature (status lists are
// public resources). The JWT payload is the second dot-separated segment.
func parseIETFStatusListJWT(jwtString string) (*common.IETFStatusList, error) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It skips JWT signature verification. The specification (5.1) requires that Relying Parties MUST reject JWTs with invalid signatures (8.3, step 3a). Before using the payload, the signature must be verified using the issuer's public key (resolved via the iss claim together with kid/JWK). Without this verification, any intermediary could forge the status list.

return nil, fmt.Errorf("%w: JWT payload JSON unmarshal failed: %v", ErrorStatusListUnparseable, err)
}

statusListRaw, ok := claims[common.IETFStatusListKey]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JWT sub claim must also be validated against the URI used to fetch it (8.3, step 4a). Extract claims["sub"] and compare it with entry.URI. If they do not match, the JWT must be rejected with an error.

}

var claims map[string]interface{}
if err := json.Unmarshal(payload, &claims); err != nil {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exp claim is not validated. The specification (8.3, step 4c) requires checking whether the Status List Token has expired when the exp claim is present. Extract claims["exp"] and reject the token if time.Now().After(exp)

// Token Status List JWT without verifying the signature (status lists are
// public resources). The JWT payload is the second dot-separated segment.
func parseIETFStatusListJWT(jwtString string) (*common.IETFStatusList, error) {
parts := strings.Split(strings.TrimSpace(jwtString), ".")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JWT header (parts[0]) is neither decoded nor validated. The specification (5.1) requires the typ header to be "statuslist+jwt". Decode parts[0] and reject the JWT if the typ value is not statuslist+jwt.


// parseIETFBits converts the `bits` value from a status list JWT payload
// to an int. JSON numbers arrive as float64.
func parseIETFBits(raw interface{}) (int, error) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseIETFBits currently accepts any positive value, but 4.1 restricts valid values to {1, 2, 4, 8}. Values outside this set produce incorrect results during bit unpacking, so validation should be added.

Additionally, when bits is nil, the function falls back to DefaultStatusSizeBits. However, the bits claim is REQUIRED (4.2), so this case should return an error instead of using a default value.

return nil, err
}

c.cache.Set(uri, statusList, c.expiry)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ttl claim of the Status List JWT is currently ignored. The specification (8.3, step 4d) recommends using ttl to determine when the cached Status List should be refreshed.

IETFStatusList struct should include a TTL field, and FetchIETF should use min(ttl, configExpiry) as the cache TTL instead of always using the fixed c.expiry value.

entry.StatusListCredential = s
}
if idx, ok := obj[StatusListEntryKeyStatusListIndex]; ok && idx != nil {
parsed, err := parseStatusListIndex(idx)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just notice this. statusListIndex is REQUIRED by the W3C Bitstring Status List specification. If it is missing, the current implementation silently defaults to index 0, which may result in accepting a credential without validating the intended status list entry.

Instead, I think the implementation should return ErrorStatusListEntryMalformed when the statusListIndex field is absent.

//
// A non-nil error is returned when the credential does not expose an
// `encodedList` string on at least one subject.
func extractStatusListFields(statusCred *common.Credential) (encodedList string, statusPurpose string, err error) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extractStatusListFields does not verify that the fetched credential is of type BitstringStatusListCredential or StatusList2021Credential. As a result, any Verifiable Credential containing a credentialSubject.encodedList would be accepted.

The implementation should validate Contents().Types and reject the credential if it is not one of the supported status list credential types.

if err != nil {
return nil, fmt.Errorf("%w: %v", ErrorStatusListHttpFailure, err)
}
req.Header.Set("Accept", ContentTypeCredentialJson)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Accept header only includes application/vc+ld+json. Although the implementation also supports JWT responses, it does not advertise this capability during content negotiation.

To support issuers with strict content negotiation, the Accept header should also include application/vc+jwt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

minor Should be applied for new functionality or bigger updates.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants