1- // Copyright 2020 Google LLC
1+ // Copyright 2026 Google LLC
22//
33// Licensed under the Apache License, Version 2.0 (the "License");
44// you may not use this file except in compliance with the License.
1414
1515part of 'impl_jni.dart' ;
1616
17+ _HashImpl _hmacHashFromHash (HashImpl hash) {
18+ if (hash is _HashImpl ) {
19+ return hash;
20+ }
21+ throw AssertionError ('Custom implementations of HashImpl are not supported.' );
22+ }
23+
24+ extension _HmacHashMetadata on _HashImpl {
25+ String get _hmacJcaName {
26+ return switch (_jcaName) {
27+ 'SHA-1' => 'HmacSHA1' ,
28+ 'SHA-256' => 'HmacSHA256' ,
29+ 'SHA-384' => 'HmacSHA384' ,
30+ 'SHA-512' => 'HmacSHA512' ,
31+ _ => throw AssertionError ('Unknown hash algorithm: $_jcaName ' ),
32+ };
33+ }
34+
35+ String get _hmacJwkAlg {
36+ return switch (_jcaName) {
37+ 'SHA-1' => 'HS1' ,
38+ 'SHA-256' => 'HS256' ,
39+ 'SHA-384' => 'HS384' ,
40+ 'SHA-512' => 'HS512' ,
41+ _ => throw AssertionError ('Unknown hash algorithm: $_jcaName ' ),
42+ };
43+ }
44+
45+ int get _hmacDefaultLengthBits {
46+ return switch (_jcaName) {
47+ 'SHA-1' => 160 ,
48+ 'SHA-256' => 256 ,
49+ 'SHA-384' => 384 ,
50+ 'SHA-512' => 512 ,
51+ _ => throw AssertionError ('Unknown hash algorithm: $_jcaName ' ),
52+ };
53+ }
54+ }
55+
1756final class _StaticHmacSecretKeyImpl implements StaticHmacSecretKeyImpl {
1857 const _StaticHmacSecretKeyImpl ();
1958
@@ -22,21 +61,144 @@ final class _StaticHmacSecretKeyImpl implements StaticHmacSecretKeyImpl {
2261 List <int > keyData,
2362 HashImpl hash, {
2463 int ? length,
25- }) {
26- throw UnimplementedError ('Not implemented' );
27- }
64+ }) async => _HmacSecretKeyImpl (
65+ _asUint8ListZeroedToBitLength (keyData, length),
66+ _hmacHashFromHash (hash),
67+ );
2868
2969 @override
3070 Future <HmacSecretKeyImpl > importJsonWebKey (
3171 Map <String , dynamic > jwk,
3272 HashImpl hash, {
3373 int ? length,
3474 }) {
35- throw UnimplementedError ('Not implemented' );
75+ final h = _hmacHashFromHash (hash);
76+ final key = JsonWebKey .fromJson (jwk);
77+
78+ void checkJwk (bool condition, String prop, String message) =>
79+ _checkData (condition, 'JWK property "$prop " $message ' );
80+
81+ checkJwk (key.kty == 'oct' , 'kty' , 'must be "oct"' );
82+ checkJwk (key.k != null , 'k' , 'must be present' );
83+ checkJwk (
84+ key.use == null || key.use == 'sig' ,
85+ 'use' ,
86+ 'must be "sig", if present' ,
87+ );
88+ checkJwk (
89+ key.alg == null || key.alg == h._hmacJwkAlg,
90+ 'alg' ,
91+ 'must be "${h ._hmacJwkAlg }"' ,
92+ );
93+
94+ final keyData = _jwkDecodeBase64UrlNoPadding (key.k! , 'k' );
95+ return importRawKey (keyData, hash, length: length);
3696 }
3797
3898 @override
3999 Future <HmacSecretKeyImpl > generateKey (HashImpl hash, {int ? length = 32 }) {
40- throw UnimplementedError ('Not implemented' );
100+ final h = _hmacHashFromHash (hash);
101+ length ?? = h._hmacDefaultLengthBits;
102+ final keyData = _randomBytes ((length + 7 ) ~ / 8 );
103+ return importRawKey (keyData, hash, length: length);
104+ }
105+ }
106+
107+ final class _HmacSecretKeyImpl implements HmacSecretKeyImpl {
108+ _HmacSecretKeyImpl (this ._keyData, this ._hash);
109+
110+ final Uint8List _keyData;
111+ final _HashImpl _hash;
112+
113+ @override
114+ Future <Uint8List > signBytes (List <int > data) async {
115+ return jni.using ((arena) {
116+ final mac = _createMac (arena);
117+ final input = arena.copyToJByteArray (_asUint8List (data));
118+ return _copyMacResult (arena, mac.doFinal$2 (input));
119+ });
120+ }
121+
122+ @override
123+ Future <Uint8List > signStream (Stream <List <int >> data) async {
124+ final arena = jni.Arena ();
125+ try {
126+ final mac = _createMac (arena);
127+ final buffer = jni.JByteArray (_defaultChunkSize)..releasedBy (arena);
128+ await for (final chunk in data) {
129+ _updateMacWithChunk (mac, buffer, chunk);
130+ }
131+
132+ return _copyMacResult (arena, mac.doFinal ());
133+ } finally {
134+ arena.releaseAll ();
135+ }
136+ }
137+
138+ @override
139+ Future <bool > verifyBytes (List <int > signature, List <int > data) async {
140+ return _verifySignature (await signBytes (data), signature);
141+ }
142+
143+ @override
144+ Future <bool > verifyStream (List <int > signature, Stream <List <int >> data) async {
145+ return _verifySignature (await signStream (data), signature);
146+ }
147+
148+ @override
149+ Future <Uint8List > exportRawKey () async => Uint8List .fromList (_keyData);
150+
151+ @override
152+ Future <Map <String , dynamic >> exportJsonWebKey () async {
153+ return JsonWebKey (
154+ kty: 'oct' ,
155+ use: 'sig' ,
156+ alg: _hash._hmacJwkAlg,
157+ k: _jwkEncodeBase64UrlNoPadding (_keyData),
158+ ).toJson ();
159+ }
160+
161+ Mac _createMac (jni.Arena arena) {
162+ final algorithm = _hash._hmacJcaName.toJString ()..releasedBy (arena);
163+ final keyData = arena.copyToJByteArray (_keyData);
164+ final key = SecretKeySpec (keyData, algorithm)..releasedBy (arena);
165+
166+ final mac = Mac .getInstance (algorithm);
167+ if (mac == null ) {
168+ throw AssertionError ('JCA Mac(${_hash ._hmacJcaName }) returned null' );
169+ }
170+ mac.releasedBy (arena);
171+ mac.init (key);
172+ return mac;
173+ }
174+
175+ void _updateMacWithChunk (Mac mac, jni.JByteArray buffer, List <int > chunk) {
176+ final bytes = _asUint8List (chunk);
177+ var offset = 0 ;
178+ while (offset < bytes.length) {
179+ final remaining = bytes.length - offset;
180+ final length = remaining < _defaultChunkSize
181+ ? remaining
182+ : _defaultChunkSize;
183+ buffer.setRange (0 , length, bytes, offset);
184+ mac.update$2 (buffer, 0 , length);
185+ offset += length;
186+ }
187+ }
188+
189+ Uint8List _copyMacResult (jni.Arena arena, jni.JByteArray ? result) {
190+ if (result == null ) {
191+ throw AssertionError ('JCA Mac(${_hash ._hmacJcaName }) returned null' );
192+ }
193+ result.releasedBy (arena);
194+ return result.copyToDartBytes ();
195+ }
196+
197+ bool _verifySignature (Uint8List computedMac, List <int > suppliedSignature) {
198+ return jni.using ((arena) {
199+ final computed = arena.copyToJByteArray (computedMac);
200+ final supplied = arena.copyToJByteArray (_asUint8List (suppliedSignature));
201+ return MessageDigest .isEqual (computed, supplied);
202+ });
41203 }
42204}
0 commit comments