Skip to content

Commit 051295d

Browse files
author
Athan Massouras
committed
fix: add support for multibit statuses to bitstring status list
Signed-off-by: Athan Massouras <[email protected]>
1 parent d32c3b5 commit 051295d

File tree

5 files changed

+58
-30
lines changed

5 files changed

+58
-30
lines changed

src/bit_array.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,11 +352,8 @@ def rand_and_settle(self):
352352
byte_idx = self._rand_settle(
353353
len(self.allocated.lst), lambda index: self.allocated.lst[index] < 255
354354
)
355-
print("byte_idx:\t", byte_idx)
356-
print(len(self.allocated.lst))
357355
start = byte_idx << 3
358356
end = start + 8
359-
print("start, end:\t", start, end)
360357
index = choice(self.linear_scan(start, end, lambda i: self.allocated[i] == 0))
361358
self.allocated[index] = 1
362359
self.num_allocated += 1

src/bitstring_status_list/issuer.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,14 @@ def generate_jwt(
102102
validUntil: Optional[str] = None,
103103
ttl: Optional[int] = None,
104104
issuer: Optional[str] = None,
105+
status_messages: Optional[list] = None,
106+
status_size: Optional[Bits] = None,
105107
) -> Tuple[dict, dict]:
108+
if status_purpose == "message":
109+
assert status_size is not None
110+
if status_size > 1:
111+
assert status_messages is not None
112+
106113
headers = {
107114
"kid": kid,
108115
"alg": alg,
@@ -130,6 +137,8 @@ def generate_jwt(
130137
"statusPurpose": status_purpose,
131138
"encodedList": self.status_list.to_b64(),
132139
**({"ttl": ttl} if ttl else {}),
140+
**({"statusMessages": status_messages} if status_messages else {}),
141+
**({"statusSize": status_size} if status_size else {}),
133142
}
134143
}
135144

@@ -147,6 +156,8 @@ def sign_jwt_enveloping(
147156
validUntil: Optional[str] = None,
148157
ttl: Optional[int] = None,
149158
issuer: Optional[str] = None,
159+
status_messages: Optional[list] = None,
160+
status_size: Optional[Bits] = None,
150161
) -> bytes:
151162
headers, payload = self.generate_jwt(
152163
alg=alg,
@@ -158,6 +169,8 @@ def sign_jwt_enveloping(
158169
validUntil=validUntil,
159170
ttl=ttl,
160171
issuer=issuer,
172+
status_messages=status_messages,
173+
status_size=status_size,
161174
)
162175

163176
enc_headers = dict_to_b64(headers)
@@ -177,6 +190,8 @@ def sign_jwt_embedding(
177190
validUntil: Optional[str] = None,
178191
ttl: Optional[int] = None,
179192
issuer: Optional[str] = None,
193+
status_messages: Optional[list] = None,
194+
status_size: Optional[Bits] = None,
180195
):
181196
headers, payload = self.generate_jwt(
182197
alg=None,
@@ -188,6 +203,8 @@ def sign_jwt_embedding(
188203
validUntil=validUntil,
189204
ttl=ttl,
190205
issuer=issuer,
206+
status_messages=status_messages,
207+
status_size=status_size,
191208
)
192209

193210
unsigned_payload_bytes = dict_to_b64(payload)

src/bitstring_status_list/verifier.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,16 @@ def verify_jwt(
125125
statusPurpose in status list is {credential_subject["statusPurpose"]}"
126126
)
127127

128-
# Cache returned status list as BitArray
128+
# If statusPurpose = message, ensure that a statusMessage list exists in the credential
129129
bits = self.credential_status.get("statusSize")
130+
if bits is not None and bits > 1 and self.credential_status.get("statusMessage") is None:
131+
raise StatusVerificationError("For statusSize > 1, a message must exist.")
132+
133+
if self.credential_status["statusPurpose"] == "message" and self.credential_status.get("statusMessage") is None:
134+
raise StatusVerificationError("If statusPurpose is `message`, a statusMessage field must \
135+
be included which provides the message associated with each bit.")
136+
137+
# Cache returned status list as BitArray
130138
self._bit_array = BitArray.from_b64(1 if bits is None else bits, credential_subject["encodedList"])
131139
if self._bit_array.size < min_list_length:
132140
raise StatusListLengthError(f"Bitstring status list must be at least {min_list_length} \
@@ -147,16 +155,18 @@ def get_status(self, idx: Optional[int] = None):
147155
# If purpose == message, extract the relevant message and add it to the return_dict, as
148156
# described in S. 3.2 Part 14.
149157
purpose = self.credential_status.get("statusPurpose")
150-
if purpose is not None and purpose == "message":
151-
try:
152-
for message in self.credential_status["statusMessage"]:
153-
if int(message["status"], 16) == status:
154-
return_dict["message"] = message["message"]
155-
break
156-
157-
raise StatusVerificationError(f"Status not found in message list: {self.credential_status["statusMessage"]}")
158-
except KeyError as k:
159-
raise StatusVerificationError(f"statusMessage is malformed or not present: {k}")
160-
161-
return return_dict
158+
if purpose is None or purpose != "message":
159+
return return_dict
160+
161+
# if purpose == "message"
162+
try:
163+
for message in self.credential_status["statusMessage"]:
164+
if int(message["status"], 16) == status:
165+
return_dict["message"] = message["message"]
166+
return return_dict
167+
168+
except KeyError as k:
169+
raise StatusVerificationError(f"statusMessage is malformed or not present: {k}")
170+
171+
raise StatusVerificationError(f"Status {status} not found in message list: {self.credential_status["statusMessage"]}")
162172

tests/test_bitstring_status_list.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,26 +122,30 @@ def test_status_message():
122122
for idx in bitstring.take_n(50):
123123
bitstring[idx] = 3
124124

125+
status_messages = [
126+
{"status":"0x0", "message":"0"},
127+
{"status":"0x1", "message":"1"},
128+
{"status":"0x2", "message":"2"},
129+
{"status":"0x3", "message":"3"},
130+
]
131+
125132
encoded_jwt = bitstring.sign_jwt_enveloping(
126133
signer=trivial_enveloping_signer,
127134
alg="ES256",
128135
kid="12",
129-
status_purpose="revocation",
136+
status_purpose="message",
137+
status_messages=status_messages,
138+
status_size=2,
130139
)
131140

132-
status_message = [
133-
{"status":"0x0", "message":"0"},
134-
{"status":"0x1", "message":"1"},
135-
{"status":"0x2", "message":"2"},
136-
{"status":"0x3", "message":"3"},
137-
]
138-
139141
credential_status = {
140142
"id": "https://example.com/credentials/status/3#94567",
141143
"type": "BitstringStatusListEntry",
142-
"statusPurpose": "revocation",
144+
"statusPurpose": "message",
143145
"statusListIndex": "0",
144-
"statusListCredential": "https://example.com/credentials/status/3"
146+
"statusListCredential": "https://example.com/credentials/status/3",
147+
"statusMessage": status_messages,
148+
"statusSize": 2,
145149
}
146150

147151
verifier = BitstringStatusListVerifier(credential_status)

tests/test_token_status_list_verifier.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def test_verify_jwt_basic(status: TokenStatusListIssuer):
8080
}
8181

8282
# Check that statuses match
83-
for i in range(len(status)):
83+
for i in range(status.status_list.size):
8484
assert status[i] == verifier.get_status(i)
8585

8686
def test_verify_jwt_expired(status: TokenStatusListIssuer):
@@ -117,7 +117,7 @@ def test_verify_jwt_es256(status: TokenStatusListIssuer, es256_signer, es256_ver
117117
verifier.jwt_verify(payload.encode(), es256_verifier)
118118

119119
# Check that values match
120-
for i in range(len(status)):
120+
for i in range(status.status_list.size):
121121
assert status[i] == verifier.get_status(i)
122122

123123
def test_verify_cwt_basic(status: TokenStatusListIssuer):
@@ -154,7 +154,7 @@ def test_verify_cwt_basic(status: TokenStatusListIssuer):
154154
}
155155

156156
# Check that values match
157-
for i in range(len(status)):
157+
for i in range(status.status_list.size):
158158
assert status[i] == verifier.get_status(i)
159159

160160
def test_verify_cwt_expired(status: TokenStatusListIssuer):
@@ -191,5 +191,5 @@ def test_verify_cwt_es256(status: TokenStatusListIssuer, es256_signer, es256_ver
191191
verifier.cwt_verify(token, es256_verifier)
192192

193193
# Check that values match
194-
for i in range(len(status)):
194+
for i in range(status.status_list.size):
195195
assert status[i] == verifier.get_status(i)

0 commit comments

Comments
 (0)