Skip to content

Commit c59eb72

Browse files
authored
feat(impl_jni): add JCA backend skeleton and digest path (#296)
* feat(impl_jni): add JCA backend skeleton and digest path * fix(impl_jni): address JNI digest review feedback; markTestSkipped, slient JNI setup issue, JString release issue, and Android test not-precise naming
1 parent 80b4c78 commit c59eb72

22 files changed

Lines changed: 878 additions & 3 deletions

example/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,10 @@ This application shows how to use `package:webcrypto` for computing a SHA-1
44
hash of user provided string.
55

66
Additionally, this example application provides integration tests for devices.
7+
8+
To run the Android JNI/JCA digest smoke test:
9+
10+
```sh
11+
flutter test integration_test/jni_digest_test.dart \
12+
-d emulator-name
13+
```
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import 'dart:convert';
2+
3+
import 'package:flutter_test/flutter_test.dart';
4+
import 'package:integration_test/integration_test.dart';
5+
import 'package:webcrypto/webcrypto.dart';
6+
7+
void main() {
8+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
9+
10+
testWidgets('public API SHA-256 digest matches known vector', (_) async {
11+
final data = utf8.encode('hello-world');
12+
final digest = await Hash.sha256.digestBytes(data);
13+
14+
expect(
15+
base64Encode(digest),
16+
'r6J7RNQ7Aqn+pB0TztwuQBbPz4fF2/mQ5ZNmmqjOKG0=',
17+
);
18+
});
19+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
part of 'impl_jni.dart';
16+
17+
final class _StaticAesCbcSecretKeyImpl implements StaticAesCbcSecretKeyImpl {
18+
const _StaticAesCbcSecretKeyImpl();
19+
20+
@override
21+
Future<AesCbcSecretKeyImpl> importRawKey(List<int> keyData) =>
22+
throw UnimplementedError('Not implemented');
23+
24+
@override
25+
Future<AesCbcSecretKeyImpl> importJsonWebKey(Map<String, dynamic> jwk) =>
26+
throw UnimplementedError('Not implemented');
27+
28+
@override
29+
Future<AesCbcSecretKeyImpl> generateKey(int length) =>
30+
throw UnimplementedError('Not implemented');
31+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
part of 'impl_jni.dart';
16+
17+
final class _StaticAesCtrSecretKeyImpl implements StaticAesCtrSecretKeyImpl {
18+
const _StaticAesCtrSecretKeyImpl();
19+
20+
@override
21+
Future<AesCtrSecretKeyImpl> importRawKey(List<int> keyData) =>
22+
throw UnimplementedError('Not implemented');
23+
24+
@override
25+
Future<AesCtrSecretKeyImpl> importJsonWebKey(Map<String, dynamic> jwk) =>
26+
throw UnimplementedError('Not implemented');
27+
28+
@override
29+
Future<AesCtrSecretKeyImpl> generateKey(int length) =>
30+
throw UnimplementedError('Not implemented');
31+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
part of 'impl_jni.dart';
16+
17+
final class _StaticAesGcmSecretKeyImpl implements StaticAesGcmSecretKeyImpl {
18+
const _StaticAesGcmSecretKeyImpl();
19+
20+
@override
21+
Future<AesGcmSecretKeyImpl> importRawKey(List<int> keyData) =>
22+
throw UnimplementedError('Not implemented');
23+
24+
@override
25+
Future<AesGcmSecretKeyImpl> importJsonWebKey(Map<String, dynamic> jwk) =>
26+
throw UnimplementedError('Not implemented');
27+
28+
@override
29+
Future<AesGcmSecretKeyImpl> generateKey(int length) =>
30+
throw UnimplementedError('Not implemented');
31+
}

lib/src/impl_jni/impl_jni.dart

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/// Experimental JNI/JCA backend for the Android JCA exploration branch.
16+
///
17+
/// During exploration the Android JCA branch may temporarily wire native builds
18+
/// directly to this backend. The final FFI-vs-JNI selection mechanism can be
19+
/// settled once enough primitives exist to compare behavior across backends.
20+
library;
21+
22+
import 'dart:async';
23+
import 'dart:typed_data';
24+
25+
import 'package:jni/jni.dart' as jni;
26+
import 'package:webcrypto/src/impl_interface/impl_interface.dart';
27+
28+
import '../third_party/jca/generated_bindings.dart';
29+
30+
part 'impl_jni.aescbc.dart';
31+
part 'impl_jni.aesctr.dart';
32+
part 'impl_jni.aesgcm.dart';
33+
part 'impl_jni.hmac.dart';
34+
part 'impl_jni.pbkdf2.dart';
35+
part 'impl_jni.ecdh.dart';
36+
part 'impl_jni.ecdsa.dart';
37+
part 'impl_jni.rsaoaep.dart';
38+
part 'impl_jni.hkdf.dart';
39+
part 'impl_jni.rsapss.dart';
40+
part 'impl_jni.rsassapkcs1v15.dart';
41+
part 'impl_jni.digest.dart';
42+
part 'impl_jni.random.dart';
43+
part 'impl_jni.utils.dart';
44+
45+
const WebCryptoImpl webCryptImpl = _WebCryptoImpl();
46+
47+
final class _WebCryptoImpl implements WebCryptoImpl {
48+
const _WebCryptoImpl();
49+
50+
@override
51+
final aesCbcSecretKey = const _StaticAesCbcSecretKeyImpl();
52+
53+
@override
54+
final aesCtrSecretKey = const _StaticAesCtrSecretKeyImpl();
55+
56+
@override
57+
final aesGcmSecretKey = const _StaticAesGcmSecretKeyImpl();
58+
59+
@override
60+
final hmacSecretKey = const _StaticHmacSecretKeyImpl();
61+
62+
@override
63+
final pbkdf2SecretKey = const _StaticPbkdf2SecretKeyImpl();
64+
65+
@override
66+
final ecdhPrivateKey = const _StaticEcdhPrivateKeyImpl();
67+
68+
@override
69+
final ecdhPublicKey = const _StaticEcdhPublicKeyImpl();
70+
71+
@override
72+
final ecdsaPrivateKey = const _StaticEcdsaPrivateKeyImpl();
73+
74+
@override
75+
final ecdsaPublicKey = const _StaticEcdsaPublicKeyImpl();
76+
77+
@override
78+
final rsaOaepPrivateKey = const _StaticRsaOaepPrivateKeyImpl();
79+
80+
@override
81+
final rsaOaepPublicKey = const _StaticRsaOaepPublicKeyImpl();
82+
83+
@override
84+
final hkdfSecretKey = const _StaticHkdfSecretKeyImpl();
85+
86+
@override
87+
final rsaPssPrivateKey = const _StaticRsaPssPrivateKeyImpl();
88+
89+
@override
90+
final rsaPssPublicKey = const _StaticRsaPssPublicKeyImpl();
91+
92+
@override
93+
final rsaSsaPkcs1v15PrivateKey = const _StaticRsaSsaPkcs1V15PrivateKeyImpl();
94+
95+
@override
96+
final rsaSsaPkcs1v15PublicKey = const _StaticRsaSsaPkcs1V15PublicKeyImpl();
97+
98+
@override
99+
final sha1 = const _HashImpl('SHA-1');
100+
101+
@override
102+
final sha256 = const _HashImpl('SHA-256');
103+
104+
@override
105+
final sha384 = const _HashImpl('SHA-384');
106+
107+
@override
108+
final sha512 = const _HashImpl('SHA-512');
109+
110+
@override
111+
final random = const _RandomImpl();
112+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
part of 'impl_jni.dart';
16+
17+
final class _HashImpl implements HashImpl {
18+
const _HashImpl(this._jcaName);
19+
20+
final String _jcaName;
21+
22+
@override
23+
Future<Uint8List> digestBytes(List<int> data) async {
24+
return jni.using((arena) {
25+
final algorithm = jni.JString.fromString(_jcaName)..releasedBy(arena);
26+
final digest = MessageDigest.getInstance(algorithm);
27+
if (digest == null) {
28+
throw operationError('JCA MessageDigest($_jcaName) is unavailable');
29+
}
30+
digest.releasedBy(arena);
31+
32+
final input = jni.JByteArray.from(data)..releasedBy(arena);
33+
final result = digest.digest$2(input);
34+
if (result == null) {
35+
throw operationError('JCA MessageDigest($_jcaName) returned null');
36+
}
37+
result.releasedBy(arena);
38+
39+
return result.copyToDartBytes();
40+
});
41+
}
42+
43+
@override
44+
Future<Uint8List> digestStream(Stream<List<int>> data) async {
45+
final digest = jni.using((arena) {
46+
final algorithm = jni.JString.fromString(_jcaName)..releasedBy(arena);
47+
final digest = MessageDigest.getInstance(algorithm);
48+
if (digest == null) {
49+
throw operationError('JCA MessageDigest($_jcaName) is unavailable');
50+
}
51+
return digest;
52+
});
53+
54+
try {
55+
await for (final chunk in data) {
56+
jni.using((arena) {
57+
final input = jni.JByteArray.from(chunk)..releasedBy(arena);
58+
digest.update$2(input);
59+
});
60+
}
61+
62+
final result = digest.digest();
63+
if (result == null) {
64+
throw operationError('JCA MessageDigest($_jcaName) returned null');
65+
}
66+
try {
67+
return result.copyToDartBytes();
68+
} finally {
69+
result.release();
70+
}
71+
} finally {
72+
digest.release();
73+
}
74+
}
75+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
part of 'impl_jni.dart';
16+
17+
final class _StaticEcdhPrivateKeyImpl implements StaticEcdhPrivateKeyImpl {
18+
const _StaticEcdhPrivateKeyImpl();
19+
20+
@override
21+
Future<EcdhPrivateKeyImpl> importPkcs8Key(
22+
List<int> keyData,
23+
EllipticCurve curve,
24+
) => throw UnimplementedError('Not implemented');
25+
26+
@override
27+
Future<EcdhPrivateKeyImpl> importJsonWebKey(
28+
Map<String, dynamic> jwk,
29+
EllipticCurve curve,
30+
) => throw UnimplementedError('Not implemented');
31+
32+
@override
33+
Future<(EcdhPrivateKeyImpl, EcdhPublicKeyImpl)> generateKey(
34+
EllipticCurve curve,
35+
) => throw UnimplementedError('Not implemented');
36+
}
37+
38+
final class _StaticEcdhPublicKeyImpl implements StaticEcdhPublicKeyImpl {
39+
const _StaticEcdhPublicKeyImpl();
40+
41+
@override
42+
Future<EcdhPublicKeyImpl> importRawKey(
43+
List<int> keyData,
44+
EllipticCurve curve,
45+
) => throw UnimplementedError('Not implemented');
46+
47+
@override
48+
Future<EcdhPublicKeyImpl> importSpkiKey(
49+
List<int> keyData,
50+
EllipticCurve curve,
51+
) => throw UnimplementedError('Not implemented');
52+
53+
@override
54+
Future<EcdhPublicKeyImpl> importJsonWebKey(
55+
Map<String, dynamic> jwk,
56+
EllipticCurve curve,
57+
) => throw UnimplementedError('Not implemented');
58+
}

0 commit comments

Comments
 (0)