From 4dc6de68c980b5c762bbe964cd7013244a5539b7 Mon Sep 17 00:00:00 2001 From: Moe Jangda Date: Wed, 27 Mar 2024 15:44:34 -0500 Subject: [PATCH] implement detached JWS verification --- packages/web5/lib/src/jws/jws.dart | 40 +++++++++++++++++++++------- packages/web5/test/jws/jws_test.dart | 21 +++++++++++++++ 2 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 packages/web5/test/jws/jws_test.dart diff --git a/packages/web5/lib/src/jws/jws.dart b/packages/web5/lib/src/jws/jws.dart index 236e8e7..443b6b5 100644 --- a/packages/web5/lib/src/jws/jws.dart +++ b/packages/web5/lib/src/jws/jws.dart @@ -8,7 +8,14 @@ import 'package:web5/src/jws/decoded_jws.dart'; import 'package:web5/src/jws/jws_header.dart'; class Jws { - static DecodedJws decode(String jws) { + /// Decodes a compact JWS per [RFC 7515](https://tools.ietf.org/html/rfc7515) + /// and returns a [DecodedJws] object. + /// + /// ### Note + /// `detachedPayload` is optional and should be provided if the payload + /// is detached. More information on detached payloads can be found + /// [here](https://tools.ietf.org/html/rfc7515#section-7.2). + static DecodedJws decode(String jws, {Uint8List? detachedPayload}) { final parts = jws.split('.'); if (parts.length != 3) { @@ -27,12 +34,17 @@ class Jws { } final Uint8List payload; - try { - payload = Base64Url.decode(parts[1]); - } on Exception { - throw Exception( - 'Malformed JWT. failed to decode claims', - ); + if (detachedPayload == null) { + try { + payload = Base64Url.decode(parts[1]); + } on Exception { + throw Exception( + 'Malformed JWT. failed to decode claims', + ); + } + } else { + payload = detachedPayload; + parts[1] = Base64Url.encode(detachedPayload); } final Uint8List signature; @@ -92,9 +104,19 @@ class Jws { } } - static Future verify(String jws) async { + /// Verifies a compact JWS per [RFC 7515](https://tools.ietf.org/html/rfc7515) + /// and returns a [DecodedJws] object. Throws [Exception] if verification fails. + /// + /// ### Note + /// `detachedPayload` is optional and should be provided if the payload + /// is detached. More information on detached payloads can be found + /// [here](https://tools.ietf.org/html/rfc7515#section-7.2). + static Future verify( + String jws, { + Uint8List? detachedPayload, + }) async { try { - final decodedJws = decode(jws); + final decodedJws = decode(jws, detachedPayload: detachedPayload); await decodedJws.verify(); return decodedJws; } on Exception catch (e) { diff --git a/packages/web5/test/jws/jws_test.dart b/packages/web5/test/jws/jws_test.dart new file mode 100644 index 0000000..4ff0aba --- /dev/null +++ b/packages/web5/test/jws/jws_test.dart @@ -0,0 +1,21 @@ +import 'dart:typed_data'; + +import 'package:web5/web5.dart'; +import 'package:test/test.dart'; + +void main() { + group('Jws', () { + test('should successfuly sign & verify detached compact jws', () async { + final did = await DidJwk.create(); + final payload = Uint8List.fromList('hello'.codeUnits); + final compactJws = + await Jws.sign(did: did, payload: payload, detachedPayload: true); + + final parts = compactJws.split('.'); + expect(parts.length, equals(3)); + expect(parts[1], equals('')); + + expect(Jws.verify(compactJws, detachedPayload: payload), completes); + }); + }); +}