From 22639779b6a58a2cfa3a286830453e7bc5561a3c Mon Sep 17 00:00:00 2001 From: Carlos Rincon <“carlos.r@affinidi.com”> Date: Tue, 14 Jan 2025 21:27:59 -0500 Subject: [PATCH 1/5] feat: dart auth provider tests --- .gitignore | 9 +- .../auth_provider/lib/src/auth_provider.dart | 5 +- .../auth_provider/lib/src/iam_client.dart | 24 + .../auth_provider/lib/src/jwt_helper.dart | 15 +- packages/dart/auth_provider/pubspec.yaml | 2 + .../test/auth_provider_test.dart | 64 ++- .../auth_provider/test/jwt_helper_test.dart | 193 +++++++ .../test/pem/openssl-rsa2048-aes128.pem | 30 + .../test/pem/openssl-rsa2048.pem | 28 + .../test/pem/openssl-rsa4096.pem | 52 ++ .../auth_provider/test/pem/ssh-rsa2048.pem | 27 + .../test/pem/ssh-rsa4096-encrypted.pem | 50 ++ .../auth_provider/test/pem/ssh-rsa4096.pem | 49 ++ .../dart/common/example/common_example.dart | 20 +- tests/integration/dart/analysis_options.yaml | 30 + tests/integration/dart/pubspec.lock | 511 ++++++++++++++++++ tests/integration/dart/pubspec.yaml | 17 + .../dart/test/auth_provider_test.dart | 73 +++ .../dart/test/elements_public_key_test.dart | 39 ++ tests/integration/dart/test/environment.dart | 40 ++ 20 files changed, 1258 insertions(+), 20 deletions(-) create mode 100644 packages/dart/auth_provider/lib/src/iam_client.dart create mode 100644 packages/dart/auth_provider/test/jwt_helper_test.dart create mode 100644 packages/dart/auth_provider/test/pem/openssl-rsa2048-aes128.pem create mode 100644 packages/dart/auth_provider/test/pem/openssl-rsa2048.pem create mode 100644 packages/dart/auth_provider/test/pem/openssl-rsa4096.pem create mode 100644 packages/dart/auth_provider/test/pem/ssh-rsa2048.pem create mode 100644 packages/dart/auth_provider/test/pem/ssh-rsa4096-encrypted.pem create mode 100644 packages/dart/auth_provider/test/pem/ssh-rsa4096.pem create mode 100644 tests/integration/dart/analysis_options.yaml create mode 100644 tests/integration/dart/pubspec.lock create mode 100644 tests/integration/dart/pubspec.yaml create mode 100644 tests/integration/dart/test/auth_provider_test.dart create mode 100644 tests/integration/dart/test/elements_public_key_test.dart create mode 100644 tests/integration/dart/test/environment.dart diff --git a/.gitignore b/.gitignore index 6c0a6fb75..b447d3967 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,11 @@ testem.log Thumbs.db .nx -.vscode \ No newline at end of file +.vscode + +# Created by `dart pub` +.dart_tool/ + +# env files +.env* +!.env.example diff --git a/packages/dart/auth_provider/lib/src/auth_provider.dart b/packages/dart/auth_provider/lib/src/auth_provider.dart index 706d8a6a2..6cc2315d6 100644 --- a/packages/dart/auth_provider/lib/src/auth_provider.dart +++ b/packages/dart/auth_provider/lib/src/auth_provider.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:affinidi_tdk_auth_provider/src/iam_client.dart'; import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; import 'package:affinidi_tdk_auth_provider/src/jwt_helper.dart'; import 'package:affinidi_tdk_common/affinidi_tdk_common.dart'; @@ -40,7 +41,9 @@ class AuthProvider { if (projectScopedToken == null) { return true; } - publicKey ??= await JWTHelper.fetchPublicKey(apiGatewayUrl); + + IamClient iamClient = IamClient(apiGatewayUrl: apiGatewayUrl); + publicKey ??= await JWTHelper.fetchPublicKey(iamClient); try { JWT.verify(projectScopedToken!, publicKey!); return false; diff --git a/packages/dart/auth_provider/lib/src/iam_client.dart b/packages/dart/auth_provider/lib/src/iam_client.dart new file mode 100644 index 000000000..bbf9f20eb --- /dev/null +++ b/packages/dart/auth_provider/lib/src/iam_client.dart @@ -0,0 +1,24 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; + +class IamClient { + IamClient({required String apiGatewayUrl, http.Client? httpClient}) + : _httpClient = httpClient ?? http.Client(), + _apiGatewayUrl = apiGatewayUrl; + + final http.Client _httpClient; + final String _apiGatewayUrl; + + Future getPublicKeyJWKS() async { + final response = await _httpClient.get( + Uri.parse('$_apiGatewayUrl/iam/.well-known/jwks.json'), + headers: {'Content-Type': 'application/json'}, + ); + + if (response.statusCode != 200) { + throw Exception('Failed to fetch public key'); + } + + return jsonDecode(response.body); + } +} diff --git a/packages/dart/auth_provider/lib/src/jwt_helper.dart b/packages/dart/auth_provider/lib/src/jwt_helper.dart index 67be18723..29239c062 100644 --- a/packages/dart/auth_provider/lib/src/jwt_helper.dart +++ b/packages/dart/auth_provider/lib/src/jwt_helper.dart @@ -1,6 +1,6 @@ +import 'package:affinidi_tdk_auth_provider/src/iam_client.dart'; import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; import 'package:uuid/uuid.dart'; -import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:pointycastle/export.dart' as pce; import 'dart:typed_data'; @@ -41,17 +41,8 @@ class JWTHelper { return token; } - static Future fetchPublicKey(String apiGatewayUrl) async { - final response = await http.get( - Uri.parse('$apiGatewayUrl/iam/.well-known/jwks.json'), - headers: {'Content-Type': 'application/json'}, - ); - - if (response.statusCode != 200) { - throw Exception('Failed to fetch public key'); - } - - final data = jsonDecode(response.body); + static Future fetchPublicKey(IamClient iamClient) async { + final data = await iamClient.getPublicKeyJWKS(); if (data['keys'] == null || data['keys'].isEmpty) { throw Exception('No keys found in JWKS'); diff --git a/packages/dart/auth_provider/pubspec.yaml b/packages/dart/auth_provider/pubspec.yaml index ce849e39c..8706b409e 100644 --- a/packages/dart/auth_provider/pubspec.yaml +++ b/packages/dart/auth_provider/pubspec.yaml @@ -19,3 +19,5 @@ dev_dependencies: lints: ^5.0.0 test: ^1.24.0 dotenv: ^4.2.0 + mocktail: ^1.0.4 + path: ^1.9.1 diff --git a/packages/dart/auth_provider/test/auth_provider_test.dart b/packages/dart/auth_provider/test/auth_provider_test.dart index c2c62e362..213a696d0 100644 --- a/packages/dart/auth_provider/test/auth_provider_test.dart +++ b/packages/dart/auth_provider/test/auth_provider_test.dart @@ -1,15 +1,69 @@ +import 'dart:io'; +import 'package:path/path.dart' as path; import 'package:test/test.dart'; +import 'package:affinidi_tdk_auth_provider/affinidi_tdk_auth_provider.dart'; + +String loadPemFile(String filename) { + final testDir = Directory.current.path; + final pemPath = path.join(testDir, 'test', 'pem', filename); + return File(pemPath).readAsStringSync(); +} void main() { - group('A group of tests', () { - // final awesome = Awesome(); + group('AuthProvider Tests', () { + final mockProjectId = 'test-project-id'; + final mockTokenId = 'test-token-id'; + + late String opensslPrivateKey; + late String opensslPublicKey; + late String sshPrivateKey; + late String sshPublicKey; + + late AuthProvider authProvider; setUp(() { - // Additional setup goes here. + opensslPrivateKey = loadPemFile('1-openssl-private-key.pem'); + opensslPublicKey = loadPemFile('1-openssl-public-key.pem'); + sshPrivateKey = loadPemFile('3-ssh-private-key.pem'); + sshPublicKey = loadPemFile('3-ssh-public-key.pem'); + + authProvider = AuthProvider( + projectId: mockProjectId, + tokenId: mockTokenId, + privateKey: opensslPrivateKey); + }); + + test('loads OpenSSL key pair successfully', () { + expect(opensslPrivateKey, contains('BEGIN PRIVATE KEY')); + expect(opensslPublicKey, contains('BEGIN PUBLIC KEY')); }); - test('First Test', () { - // expect(awesome.isAwesome, isTrue); + test('loads SSH key pair successfully', () { + expect(sshPrivateKey, contains('BEGIN OPENSSH PRIVATE KEY')); + expect(sshPublicKey, contains('BEGIN RSA PUBLIC KEY')); + }); + + test('createIotaToken returns valid token and session', () { + final iotaConfigId = 'test-iota-config'; + final did = 'did:test:123'; + final sessionId = 'test-session'; + + final result = authProvider.createIotaToken( + iotaConfigId: iotaConfigId, did: did, iotaSessionId: sessionId); + + expect(result.iotaSessionId, equals(sessionId)); + expect(result.iotaJwt, isNotEmpty); + }); + + test('createIotaToken generates session ID if not provided', () { + final iotaConfigId = 'test-iota-config'; + final did = 'did:test:123'; + + final result = + authProvider.createIotaToken(iotaConfigId: iotaConfigId, did: did); + + expect(result.iotaSessionId, isNotEmpty); + expect(result.iotaJwt, isNotEmpty); }); }); } diff --git a/packages/dart/auth_provider/test/jwt_helper_test.dart b/packages/dart/auth_provider/test/jwt_helper_test.dart new file mode 100644 index 000000000..0513e941c --- /dev/null +++ b/packages/dart/auth_provider/test/jwt_helper_test.dart @@ -0,0 +1,193 @@ +import 'package:affinidi_tdk_auth_provider/src/iam_client.dart'; +import 'package:affinidi_tdk_auth_provider/src/jwt_helper.dart'; +import 'package:test/test.dart'; +import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; +import 'dart:io'; +import 'package:path/path.dart' as path; +import 'package:mocktail/mocktail.dart'; + +class MockIamClient extends Mock implements IamClient {} + +void main() { + group('JWTHelper Tests', () { + late IamClient iamClient; + late String keyOpensslRSA2048; + late String keyOpensslRSA4096; + late String keySshRSA2048; + late String keySshRSA4096; + + setUp(() { + final testDir = Directory.current.path; + keyOpensslRSA2048 = + File(path.join(testDir, 'test', 'pem', 'openssl-rsa2048.pem')) + .readAsStringSync(); + keyOpensslRSA4096 = + File(path.join(testDir, 'test', 'pem', 'openssl-rsa4096.pem')) + .readAsStringSync(); + keySshRSA2048 = File(path.join(testDir, 'test', 'pem', 'ssh-rsa2048.pem')) + .readAsStringSync(); + keySshRSA4096 = File(path.join(testDir, 'test', 'pem', 'ssh-rsa4096.pem')) + .readAsStringSync(); + }); + + group('signPayload with unencrypted key', () { + test('creates and signs valid JWT from OpenSSL RSA 2048 key', () { + final token = JWTHelper.signPayload( + audience: 'test-audience', + tokenId: 'test-token', + privateKey: keyOpensslRSA2048, + ); + + expect(token, isNotEmpty); + + final jwt = JWT.decode(token); + + expect(jwt.payload['aud'], equals('test-audience')); + expect(jwt.payload['iss'], equals('test-token')); + expect(jwt.payload['sub'], equals('test-token')); + expect(jwt.payload['jti'], isNotEmpty); + expect(jwt.payload['iat'], isA()); + expect(jwt.payload['exp'], isA()); + expect(jwt.payload['exp'] - jwt.payload['iat'], equals(5 * 60)); + }); + + test('creates and signs valid JWT from OpenSSL RSA 4096 key', () { + final token = JWTHelper.signPayload( + audience: 'test-audience', + tokenId: 'test-token', + privateKey: keyOpensslRSA4096, + ); + + expect(token, isNotEmpty); + + final jwt = JWT.decode(token); + + expect(jwt.payload['aud'], equals('test-audience')); + expect(jwt.payload['iss'], equals('test-token')); + expect(jwt.payload['sub'], equals('test-token')); + expect(jwt.payload['jti'], isNotEmpty); + expect(jwt.payload['iat'], isA()); + expect(jwt.payload['exp'], isA()); + expect(jwt.payload['exp'] - jwt.payload['iat'], equals(5 * 60)); + }); + + test('creates and signs valid JWT from ssh-keygen RSA 2048 key', () { + final token = JWTHelper.signPayload( + audience: 'test-audience', + tokenId: 'test-token', + privateKey: keySshRSA2048, + ); + + expect(token, isNotEmpty); + + final jwt = JWT.decode(token); + + expect(jwt.payload['aud'], equals('test-audience')); + expect(jwt.payload['iss'], equals('test-token')); + expect(jwt.payload['sub'], equals('test-token')); + expect(jwt.payload['jti'], isNotEmpty); + expect(jwt.payload['iat'], isA()); + expect(jwt.payload['exp'], isA()); + expect(jwt.payload['exp'] - jwt.payload['iat'], equals(5 * 60)); + }); + + test('creates and signs valid JWT from ssh-keygen RSA 4096 key', () { + final token = JWTHelper.signPayload( + audience: 'test-audience', + tokenId: 'test-token', + privateKey: keySshRSA4096, + ); + + expect(token, isNotEmpty); + + final jwt = JWT.decode(token); + + expect(jwt.payload['aud'], equals('test-audience')); + expect(jwt.payload['iss'], equals('test-token')); + expect(jwt.payload['sub'], equals('test-token')); + expect(jwt.payload['jti'], isNotEmpty); + expect(jwt.payload['iat'], isA()); + expect(jwt.payload['exp'], isA()); + expect(jwt.payload['exp'] - jwt.payload['iat'], equals(5 * 60)); + }); + + test('includes keyId in header when provided', () { + final token = JWTHelper.signPayload( + audience: 'test-audience', + tokenId: 'test-token', + privateKey: keyOpensslRSA2048, + keyId: 'test-key-id', + ); + + final jwt = JWT.decode(token); + expect(jwt.header!['kid'], equals('test-key-id')); + }); + + test('includes additional payload when provided', () { + final token = JWTHelper.signPayload( + audience: 'test-audience', + tokenId: 'test-token', + privateKey: keyOpensslRSA2048, + additionalPayload: { + 'custom_claim': 'custom_value', + 'nested_claim': {'key': 'value'} + }, + ); + + final jwt = JWT.decode(token); + expect(jwt.payload['custom_claim'], equals('custom_value')); + expect(jwt.payload['nested_claim'], equals({'key': 'value'})); + }); + + test('uses RS256 algorithm', () { + final token = JWTHelper.signPayload( + audience: 'test-audience', + tokenId: 'test-token', + privateKey: keyOpensslRSA2048, + ); + + final jwt = JWT.decode(token); + expect(jwt.header!['alg'], equals('RS256')); + }); + + test('throws error with invalid private key', () { + expect( + () => JWTHelper.signPayload( + audience: 'test-audience', + tokenId: 'test-token', + privateKey: 'invalid-key', + ), + throwsA(isA())); + }); + }); + + group('fetchPublicKey', () { + setUp(() { + iamClient = MockIamClient(); + }); + + test('successfully parses JWKS', () async { + final mockPublicKeyJWKS = { + 'keys': [ + { + 'kid': 'a622a999-9846-48cf-a470-22759e1f435a', + 'alg': 'ES256', + 'use': 'sig', + 'kty': 'EC', + 'crv': 'P-256', + 'x': 'b3kdYEBrlWjQwY55F8MhXC97pwkjTpcQZZ09oDDBK4c', + 'y': 'wlopQwIPWuT55M3ZfCDZdoBs1nh2kwEvzPjnkakf96U' + } + ] + }; + + when(() => iamClient.getPublicKeyJWKS()).thenAnswer( + (_) async => mockPublicKeyJWKS, + ); + + final publicKey = await JWTHelper.fetchPublicKey(iamClient); + expect(publicKey, isA()); + }); + }); + }); +} diff --git a/packages/dart/auth_provider/test/pem/openssl-rsa2048-aes128.pem b/packages/dart/auth_provider/test/pem/openssl-rsa2048-aes128.pem new file mode 100644 index 000000000..59ca7f7b6 --- /dev/null +++ b/packages/dart/auth_provider/test/pem/openssl-rsa2048-aes128.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQ8KJC9coAjae+YbnN +5D3/DgICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEMY+Dtssy+iN8uMn +bRwTi5YEggTQG7Fq68P7Xmd5+hvfOHhjaZzsaYh24VmcEUmfv/DeKkJVJCeLWaeK +iLbZUjtiCUBl7U3Dulgfmf6LWrbAnLlfpPb792N5xjTOtChNilFhNPK3eBW6Ax3F +Im34Q9D+Hq7Ogmw7d2cVCC0MTaCH88PGt+3YjGZlNErvvECjn1Dl3xJ/WieAGBjw +dCHaABGyd87LjF4ytu60nbkv0isPsFxHUqevrNGRPlPDX8HfUlOoQlifXF0CRkC4 +99tOcsg6HYIqrgKoUXL5SGm/IgG8ItgLiBznPkOWyKk3J5giyt6wSpKfCTDjVRLR +IbIuLO5c8Toosj47+yt3gt9RwIsTU9O0ouPwer8C8LQUerGUxvU84qFzoakGW9/W +r0lLmRqqwNid9EVTVEMIYCDZCnJXWc9p4zJJzndlcqeGEgUUZHmm4UiNmuXANUqE +kNx29VzP93cERn2zMxLNnXie3Z7KhHXmHTOtqIu/avsNG1ZTOP5Crt5ue4Kkrbjv +YNsWtoYjOCoHCRjBi1hveaiNelacp9bGZTlCHubF3ddZyXBlln+Dh3Os4oi+fQL5 +kZnmn0BEs5M2uExK4NvdBJOadGPFU77mufIckpgp2DKq93nQ/BrHRfCveBQqduF+ +7SHZyrawTVP3yjq6vl1I1/7mP3vXCbB5QQaQCka46OSnb0nWarerTS4k3b/KNUUV +yVveM6z6FcXnesOkDT5sDmpfpxjmmkAA0YFg59tU2zYMIDwEAuJR0i2ZZBFjqQNk +A0H3BZJJAzHoZLBusPArJD5puKBGlsjLDKjBXvSWKD/0MqigzAaFXLkriGY+mHD8 +FfVrN6h0qEwP9oYAaMb3n7KaPn635an9s1E8aBY0gp388K76iwRx102YSYM/QSjM +TGNHh5Au3m0zISpmoMPb13BtoLWHqPNv1WOiafvBFPVPR2lq8q08DZG9h++6NuY4 +StWQxYl0XNxXbOVFdznxXNu+rtdK4h+oDm1MJLcbrQsN11TfQNuqOYxniHBTzPpk +afvf9hDtJhtvNAKrkydE5UnlwSh2Ahvu5dIFIfQgTV3hc3gEP4y5NvxGoo3McRIm +VO13Q36GdERF24wLOeF26BSo04kmHftPnOiV+iukOCDmYWN8SMGBwNedrSVkfdSJ +cfwvo4bH2mFHjzFvgvDo9isYZaHfX4L7IhpYQ0uWAHSgwaGuxpnsAuQ3j6c8QiUL +iBWsiqkINHYCYXtd8aBKFxRdMc7MU8/IuUiBrCuBEuUxFDjPK2Y2y9v0QMFr9kd/ +rFO25Y6R6jZ4VoH12r/EidnDJA+/Ze+t8cpNEXx9CyAcNnJ92zc7OZVoKroZuniC +69YKND1guVGYFkuJgDXaKbthslSIDGU/bymTsO9Kb04rd2M/Am0sH9SI+8YiM8pJ +rtO9fXy2bl1yzz9ABB10xEaRj7vTfz1lxZ4hokjWrLQWMkV5thY50oPfHlByLPLK +vLCYkSWOFGOQpH3FOq2noSflu4rdOr4Q236g8Hf7GaARNeApfmJBx+2aOeKyZFXJ +ETaeMrsKJFKHLFhCV3JdCX7UM8t1ajqxfC24CqKFPeWme4RoPuNEoYLMreaSdhMd +1Nk06xcYBGllRUEz9ATqaTVI4zZ9/OyiZidJ67c0y3nINUs7/7zF4lE= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/packages/dart/auth_provider/test/pem/openssl-rsa2048.pem b/packages/dart/auth_provider/test/pem/openssl-rsa2048.pem new file mode 100644 index 000000000..e335a0ba4 --- /dev/null +++ b/packages/dart/auth_provider/test/pem/openssl-rsa2048.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCfrwnf+Z0OeCMi +ZoeexcCbcsAT6oUddM7AXoBY7s1CrTUqx5VGy6ea6FTYpsu2XsOcKRzRyl69mmKk +cVKUvb08rZcj8wWPp3hY3E0kzg+A0C+qOj6XPX9xGzZynuQCrMjgO/4bZHK50Upx +ajv7oXa2LxGteR7DAT6/9PRFL/0oyc7iKHcG2Fvb97cCC8blbqntnFqdwus7gKLy +XSuBte9llT/MTv72NsVyIwDhK60aHVgkvvw/JfqBIMMxiZlYgyRJgpV+DtuDi5yn +60S6X26vbRWMfs1K1VlfAXiJIOTCY42GZNmqun6LINXZ5st3coh3Nrzm3tpLMEyj +105wvDNPAgMBAAECggEAGCOKt5vxPQW2MfYT3FGCiz0ughQ6qThGJNhkSI1Y5BJw +7hWboEcbABTM9q9ILjpnEY05eRFBfyH+dWNYG3oPSEPpjBqppYyoaa5rzvuDZnKS +MgZ5/bzjLgLyGSOhzjG2cAdBo3xsx2A3A6wOgzxmSKYW4wVjPBFNHhF2d5sIhwi1 +jPgW0fCHJQuUDjlUU8gdSqhMchCCVuQyWL31W9NgMn6OCFdh8cjA8D6AKxsCW0mp +e/myUD0oWxrhJe1nlGan+nsnQZo5TpyFjkzH3yuK/NNmOX8ajS840anWzdHvX283 +WoP2PrmKUGdqtr1CGbd7EdjTiNMWIypzTIm3ba1cYQKBgQDWWGepr5aE0OZ7zfaR +RcCCLawFTVAqopBcx1HkXFk8ofKyOwDPmbeYgrHtr0Ot2kAN/3mOjtNrcChmtkpw ++poSovlGn0SIq9IYJbiWb30EitrRqjkqPAVn9admiGwFZFmwQVWF8+1QXEQCBCvi +1z5mNvzB5KxKDlVKHxD3NqnM8QKBgQC+tz3MEu0ovEmPf+oKudhZwbTfPY9mGCL3 +dcrNwPQYmq86GycbaIU0F9g4Bw7zy2bRU12bh/k9RmBCNPJffT+maODCLRL2i5qB +Qd9rPPi8kF39KWBG7gA9DJFvHT2xJ8I28U2iWq+yEs6WH8oHiA3ENaJ4CHXuG+t1 +RppCDa8EPwKBgEduDp2ttitssmJvsMuYwx6eucTKjvymUBWbFt9TJyndjlN29j44 +q8ZXR5Q94//7y3zetlObpTkYl14jQYuE9/Nd/FRcnyosmEcTyv/XB4KMA3/7ijFY +7zRF2ROCQv1JA9qI60dIkr1FAiTp3vYpZNILYQ/8dK35ONMKp0y7GrsBAoGASGYp +qIH60/7+ceJeR6obbp9xeVnWSSyagZSO46L/RyPZp1ZNd0MrZgYzR7muPHCX3Jko +LPXmcRN5UUjmRce3VQX1ZOFVlJCUm8MU/JHN309yzrtZWDPblVFjGGpiVBFC0jay +gRKqJhCrqiPxPwCwMS8nOSgFFNo2fXPK9Y5aRWMCgYEAhbMmW9S/H78F01u/xwjG +CJhnYj3Ke8033Nu7q4XANH23/jRb6B32QM0CFARM2FkbOR1jozRghgsCJFhL7pcG +pbTBpCSSXOJ3oLHFERWUg9dZOU01hkLc/MsU5XJKYdy+nEIpZymg0kpGyagC0IN9 +YfFw+va3KhGX3fJEmRoAvTA= +-----END PRIVATE KEY----- diff --git a/packages/dart/auth_provider/test/pem/openssl-rsa4096.pem b/packages/dart/auth_provider/test/pem/openssl-rsa4096.pem new file mode 100644 index 000000000..5b596177b --- /dev/null +++ b/packages/dart/auth_provider/test/pem/openssl-rsa4096.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +4GRX2gkZaFVx2nHe8Qb8kdqzFljK +-----END PRIVATE KEY----- diff --git a/packages/dart/auth_provider/test/pem/ssh-rsa2048.pem b/packages/dart/auth_provider/test/pem/ssh-rsa2048.pem new file mode 100644 index 000000000..451b8b5b6 --- /dev/null +++ b/packages/dart/auth_provider/test/pem/ssh-rsa2048.pem @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAqyxJwBjoG7Z6I6giEryc8FpggzH8oBLdU6wM/s3aEwJIiQ+9cHkV +z5gzshBFlRPN1VjJeGqVWWxyDZIugtFQZv3iFPa2ZNOTLR7Qu/6njaKclvi78zoTxzqoCI +AQWouQ5n/+D9nFa1zI3WUPnl6BTUM2wCmOjJqL0tLNJlbcmScIFuKtlxNyInqxIRwvc9Gt +e5+aeT4BIOohDxht66/Gr45EQclWEFlVJWafXLfJ1aad4D7a8Yu2uoChVARA47JIGsoXqd +pTtL1kWmZmvnqDAamnKwbN3VXqvKeAffqdfEweF6JEUJUZhUa5Bk4Ijp43wHPvIzbvkhfJ +P4eDG5qkSQAAA9j4GEJQ+BhCUAAAAAdzc2gtcnNhAAABAQCrLEnAGOgbtnojqCISvJzwWm +CDMfygEt1TrAz+zdoTAkiJD71weRXPmDOyEEWVE83VWMl4apVZbHINki6C0VBm/eIU9rZk +05MtHtC7/qeNopyW+LvzOhPHOqgIgBBai5Dmf/4P2cVrXMjdZQ+eXoFNQzbAKY6MmovS0s +0mVtyZJwgW4q2XE3IierEhHC9z0a17n5p5PgEg6iEPGG3rr8avjkRByVYQWVUlZp9ct8nV +pp3gPtrxi7a6gKFUBEDjskgayhep2lO0vWRaZma+eoMBqacrBs3dVeq8p4B9+p18TB4Xok +RQlRmFRrkGTgiOnjfAc+8jNu+SF8k/h4MbmqRJAAAAAwEAAQAAAQBacM0KzV4d/l0sZ9KQ ++c0mBWqHBytGXJFKe1ZmbtdxQbyXhpR+T8vhYra1t3k8WjlgJ0hT8mS02eKtHvaqMfP8Zt +pEX1JhlZRmu6hoHBXldOytrACKc74tfuV3kEqTvLgzwZ64O0TfBCgxKguFjsNc0k2kXJCv +45xLKQwx3KUz2ZjK0Ssz9UFT+FVXoLgwLGSWHWle056tjoOhNn4l43t0nvAjRQ5eBME+tb +vkAdi24iIaz8CANynR3hkUGEGB3ryj4YsfmfZ3Oo15KYP6tUrqKuywg8jwHp+NROR49gaA +Ve++udKrKWc5EIZW7k4vKp8ztnfB9heG8cRXn6YqY8dNAAAAgQDaBY+hTwO15kT3lYB+nM +FYpc3drdNbZbK0Mywm81Rk53tnrwtKaC4UYXK3Oya8Zsmcc7v9ReYEZXjNww8jd5d71y6O +YMqCJhc+R0yZvSofjcRIs3jwDCzaeCmvvhqrCpuiBvJLWZ5NronbuZK12Q2X/lo1r48Bgd +fxiwODK6ER3AAAAIEA4b15yGqg7QttyeS4ksmKFO41vXXx/0lVLqCVSZrvexynlm9Ss3pG +HGKigou45jSYZeRad35rC9qMyB2p34YC16oC65N3GvlUiteu5oZ5IEU9Surch9pcn77gW/ +KkmQ1Vlh/L/DJyCgFs2WGetyMGm37oTAGT16URjO458vmOjWsAAACBAMIeR7yz92ahABLv +yBe1edOEC+iWszm/S02esSk8hN/gznBaxKJ9VF4Xp0wcnxHQzvxd2fglmVJ7pNbO4m4+Z0 +WKlfxuPSWTQBBzXm2URNuQpmtSZnyaUDNx9tR6dhmPaHFqEhTVAvMbfgbFf3gemiiD1Y6e +rWGJ2BX/E8fCGa4bAAAAG2Nhcmxvc0BNYWMtQ1dGVjY3VDk3TC5sb2NhbAECAwQFBgc= +-----END OPENSSH PRIVATE KEY----- diff --git a/packages/dart/auth_provider/test/pem/ssh-rsa4096-encrypted.pem b/packages/dart/auth_provider/test/pem/ssh-rsa4096-encrypted.pem new file mode 100644 index 000000000..132ad4c4c --- /dev/null +++ b/packages/dart/auth_provider/test/pem/ssh-rsa4096-encrypted.pem @@ -0,0 +1,50 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +-----END OPENSSH PRIVATE KEY----- diff --git a/packages/dart/auth_provider/test/pem/ssh-rsa4096.pem b/packages/dart/auth_provider/test/pem/ssh-rsa4096.pem new file mode 100644 index 000000000..50c380105 --- /dev/null +++ b/packages/dart/auth_provider/test/pem/ssh-rsa4096.pem @@ -0,0 +1,49 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +-----END OPENSSH PRIVATE KEY----- diff --git a/packages/dart/common/example/common_example.dart b/packages/dart/common/example/common_example.dart index eb9d51990..bf9e2943a 100644 --- a/packages/dart/common/example/common_example.dart +++ b/packages/dart/common/example/common_example.dart @@ -1,3 +1,21 @@ +import 'package:affinidi_tdk_common/affinidi_tdk_common.dart'; + void main() { - print("hello world"); + final Environment local = Environment.environments[EnvironmentType.local]!; + final Environment development = + Environment.environments[EnvironmentType.development]!; + + final localApiGwUrl = Environment.fetchApiGwUrl(local); + final devApiGwUrl = Environment.fetchApiGwUrl(development); + final prodApiGwUrl = Environment.fetchApiGwUrl(); // Defaults to prod + print("Local API Gateway URL: $localApiGwUrl"); + print("Development API Gateway URL: $devApiGwUrl"); + print("Production API Gateway URL: $prodApiGwUrl"); + + final localWebVaultUrl = VaultUtils.fetchWebVaultUrl(local); + final devWebVaultUrl = VaultUtils.fetchWebVaultUrl(development); + final prodWebVaultUrl = VaultUtils.fetchWebVaultUrl(); // Defaults to prod + print("Local Web Vault URL: $localWebVaultUrl"); + print("Development Web Vault URL: $devWebVaultUrl"); + print("Production Web Vault URL: $prodWebVaultUrl"); } diff --git a/tests/integration/dart/analysis_options.yaml b/tests/integration/dart/analysis_options.yaml new file mode 100644 index 000000000..dee8927aa --- /dev/null +++ b/tests/integration/dart/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/tests/integration/dart/pubspec.lock b/tests/integration/dart/pubspec.lock new file mode 100644 index 000000000..c9b4164c4 --- /dev/null +++ b/tests/integration/dart/pubspec.lock @@ -0,0 +1,511 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "88399e291da5f7e889359681a8f64b18c5123e03576b01f32a6a276611e511c3" + url: "https://pub.dev" + source: hosted + version: "78.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.3" + adaptive_number: + dependency: transitive + description: + name: adaptive_number + sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + affinidi_tdk_auth_provider: + dependency: "direct dev" + description: + path: "../../../packages/dart/auth_provider" + relative: true + source: path + version: "1.0.0" + affinidi_tdk_common: + dependency: "direct dev" + description: + path: "../../../packages/dart/common" + relative: true + source: path + version: "1.0.0" + affinidi_tdk_login_configuration_client: + dependency: "direct dev" + description: + path: "../../../clients/dart/login_configuration_client" + relative: true + source: path + version: "1.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "62899ef43d0b962b056ed2ebac6b47ec76ffd003d5f7c4e4dc870afe63188e33" + url: "https://pub.dev" + source: hosted + version: "7.1.0" + args: + dependency: transitive + description: + name: args + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + url: "https://pub.dev" + source: hosted + version: "2.6.0" + async: + dependency: transitive + description: + name: async + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + url: "https://pub.dev" + source: hosted + version: "2.12.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 + url: "https://pub.dev" + source: hosted + version: "1.11.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + dart_jsonwebtoken: + dependency: "direct dev" + description: + name: dart_jsonwebtoken + sha256: "866787dc17afaef46a9ea7dd33eefe60c6d82084b4a36d70e8e788d091cd04ef" + url: "https://pub.dev" + source: hosted + version: "2.14.2" + dotenv: + dependency: "direct dev" + description: + name: dotenv + sha256: "379e64b6fc82d3df29461d349a1796ecd2c436c480d4653f3af6872eccbc90e1" + url: "https://pub.dev" + source: hosted + version: "4.2.0" + ed25519_edwards: + dependency: transitive + description: + name: ed25519_edwards + sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + http: + dependency: transitive + description: + name: http + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + url: "https://pub.dev" + source: hosted + version: "1.2.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + intl: + dependency: transitive + description: + name: intl + sha256: "00f33b908655e606b86d2ade4710a231b802eec6f11e87e4ea3783fd72077a50" + url: "https://pub.dev" + source: hosted + version: "0.20.1" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + url: "https://pub.dev" + source: hosted + version: "0.7.1" + lints: + dependency: "direct dev" + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + macros: + dependency: transitive + description: + name: macros + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" + url: "https://pub.dev" + source: hosted + version: "0.1.3-main.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + pointycastle: + dependency: "direct dev" + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test: + dependency: "direct dev" + description: + name: test + sha256: "8391fbe68d520daf2314121764d38e37f934c02fd7301ad18307bd93bd6b725d" + url: "https://pub.dev" + source: hosted + version: "1.25.14" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + test_core: + dependency: transitive + description: + name: test_core + sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" + url: "https://pub.dev" + source: hosted + version: "0.6.8" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web: + dependency: transitive + description: + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.6.0 <4.0.0" diff --git a/tests/integration/dart/pubspec.yaml b/tests/integration/dart/pubspec.yaml new file mode 100644 index 000000000..2c2ee50a8 --- /dev/null +++ b/tests/integration/dart/pubspec.yaml @@ -0,0 +1,17 @@ +name: dart_tdk_integration_tests + +environment: + sdk: ^3.6.0 + +dev_dependencies: + affinidi_tdk_auth_provider: + path: ../../../packages/dart/auth_provider + affinidi_tdk_common: + path: ../../../packages/dart/common + affinidi_tdk_login_configuration_client: + path: ../../../clients/dart/login_configuration_client + dotenv: ^4.2.0 + lints: ^5.0.0 + test: ^1.24.0 + pointycastle: ^3.9.1 + dart_jsonwebtoken: ^2.12.0 diff --git a/tests/integration/dart/test/auth_provider_test.dart b/tests/integration/dart/test/auth_provider_test.dart new file mode 100644 index 000000000..ec3d0e1f8 --- /dev/null +++ b/tests/integration/dart/test/auth_provider_test.dart @@ -0,0 +1,73 @@ +import 'package:test/test.dart'; +import 'package:affinidi_tdk_auth_provider/affinidi_tdk_auth_provider.dart'; +import 'package:affinidi_tdk_login_configuration_client/api.dart'; +import 'environment.dart'; + +void main() { + group('Auth Provider Integration Tests', () { + final expiredToken = + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImE2MjJhOTk5LTk4NDYtNDhjZi1hNDcwLTIyNzU5ZTFmNDM1YSJ9.eyJjbGllbnRfaWQiOiJmNGVjMDhjOC1kMmRhLTQ0ZmUtOTlmYi01YWNkOTc0NDQ1NTMiLCJzY3AiOlsiZWxlbWVudHNfYXBpIl0sInN1YiI6ImFyaTppYW06OjUyNWYxYTQyLWRjN2MtNGQ3ZC04YTc5LWQ5OGFjM2Q2OWJhZDp0b2tlbi9mNGVjMDhjOC1kMmRhLTQ0ZmUtOTlmYi01YWNkOTc0NDQ1NTMvY2xpZW50L2Y0ZWMwOGM4LWQyZGEtNDRmZS05OWZiLTVhY2Q5NzQ0NDU1MyIsImF1ZCI6ImVsZW1lbnRzX2FwaSIsImlzcyI6Imh0dHBzOi8vYXBzZTEuYXBpLmFmZmluaWRpLmlvL2lhbSIsImlhdCI6MTczNjg5OTIwMiwiZXhwIjoxNzM2OTAyODAyLCJqdGkiOiJhMTZmYTkxNi0wMzBkLTRhZWMtODNlNi1jZTI5ZDlkN2E3ZmIifQ.orIeJ082l2-x_IXa01KA1PXBp6ndYo2024mc-v07We8cgLhZdD0MLK-MQcoMrDzKfj6ZP3UYrOtmcPZrw6Gdrw'; + + late ProjectEnvironment env; + setUp(() { + env = getProjectEnvironment(); + }); + + test('obtain project scoped token, cache it and verify it with public key', + () async { + final authProvider = AuthProvider( + projectId: env.projectId, + tokenId: env.tokenId, + privateKey: env.privateKey, + keyId: env.keyId, + passphrase: env.passphrase, + ); + expect(authProvider.projectScopedToken, isNull); + final projectScopedToken1 = await authProvider.fetchProjectScopedToken(); + expect(projectScopedToken1, isNotEmpty); + expect(projectScopedToken1, equals(authProvider.projectScopedToken)); + expect(authProvider.publicKey, isNull); + + final projectScopedToken2 = await authProvider.fetchProjectScopedToken(); + expect(projectScopedToken2, equals(projectScopedToken1)); + expect(authProvider.publicKey, isNotNull); + }); + + test('identify expired project scoped token and request a new one', + () async { + final authProvider = AuthProvider( + projectId: env.projectId, + tokenId: env.tokenId, + privateKey: env.privateKey, + keyId: env.keyId, + passphrase: env.passphrase, + ); + expect(authProvider.projectScopedToken, isNull); + authProvider.projectScopedToken = expiredToken; + expect(authProvider.projectScopedToken, isNotEmpty); + expect(authProvider.publicKey, isNull); + + final projectScopedToken = await authProvider.fetchProjectScopedToken(); + expect(projectScopedToken, equals(authProvider.projectScopedToken)); + expect(projectScopedToken, isNotEmpty); + expect(projectScopedToken, isNot(equals(expiredToken))); + expect(authProvider.publicKey, isNotNull); + }); + + test('use auth provider with an API client', () async { + final authProvider = AuthProvider( + projectId: env.projectId, + tokenId: env.tokenId, + privateKey: env.privateKey, + keyId: env.keyId, + passphrase: env.passphrase, + ); + + final apiClient = + ApiClient(authTokenHook: authProvider.fetchProjectScopedToken); + final apiInstance = GroupApi(apiClient); + final userGroups = await apiInstance.listGroups(); + expect(userGroups, isA()); + }); + }); +} diff --git a/tests/integration/dart/test/elements_public_key_test.dart b/tests/integration/dart/test/elements_public_key_test.dart new file mode 100644 index 000000000..95287ac23 --- /dev/null +++ b/tests/integration/dart/test/elements_public_key_test.dart @@ -0,0 +1,39 @@ +import 'package:test/test.dart'; +import 'package:affinidi_tdk_auth_provider/src/iam_client.dart'; +import 'package:affinidi_tdk_auth_provider/src/jwt_helper.dart'; +import 'package:affinidi_tdk_common/affinidi_tdk_common.dart'; +import 'package:pointycastle/export.dart' as pce; +import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; + +void main() { + group('Affinidi Elements Public Key Tests', () { + test('fetches public key from development environment', () async { + final env = Environment.environments[EnvironmentType.development]!; + final devIamClient = IamClient(apiGatewayUrl: env.apiGwUrl); + final publicKey = await JWTHelper.fetchPublicKey(devIamClient); + + expect(publicKey, isA()); + expect(publicKey.key.Q, isNotNull); + expect(publicKey.key.parameters, isA()); + }); + + test('fetches public key from production environment', () async { + final env = Environment.environments[EnvironmentType.production]!; + final prodIamClient = IamClient(apiGatewayUrl: env.apiGwUrl); + final publicKey = await JWTHelper.fetchPublicKey(prodIamClient); + + expect(publicKey, isA()); + expect(publicKey.key.Q, isNotNull); + expect(publicKey.key.parameters, isA()); + }); + + test('throws exception on invalid JWKS endpoint', () { + final invalidIamClient = + IamClient(apiGatewayUrl: 'https://invalid-endpoint.example.com'); + expect( + () => JWTHelper.fetchPublicKey(invalidIamClient), + throwsA(isA()), + ); + }); + }); +} diff --git a/tests/integration/dart/test/environment.dart b/tests/integration/dart/test/environment.dart new file mode 100644 index 000000000..71c143605 --- /dev/null +++ b/tests/integration/dart/test/environment.dart @@ -0,0 +1,40 @@ +import 'package:dotenv/dotenv.dart'; + +class ProjectEnvironment { + final String projectId; + final String tokenId; + final String privateKey; + final String? keyId; + final String? passphrase; + + ProjectEnvironment({ + required this.projectId, + required this.tokenId, + required this.privateKey, + this.keyId, + this.passphrase, + }); +} + +ProjectEnvironment getProjectEnvironment() { + final env = DotEnv(includePlatformEnvironment: true)..load(['../../.env']); + + if (!env.isEveryDefined(['PROJECT_ID', 'TOKEN_ID', 'PRIVATE_KEY'])) { + throw ('Missing environment variables. Please provide PROJECT_ID, TOKEN_ID and PRIVATE_KEY'); + } + + // Workaround for dotenv multiline limitations + final privateKey = env['PRIVATE_KEY']!.replaceAll('\\n', '\n'); + final token = env['TOKEN_ID']!; + final projectId = env['PROJECT_ID']!; + final keyId = env['KEY_ID']; + final passphrase = env['PASSPHRASE']; + + return ProjectEnvironment( + projectId: projectId, + tokenId: token, + privateKey: privateKey, + keyId: keyId, + passphrase: passphrase, + ); +} From 3471cb85311dfa99fb3cd237f68a29129830d54f Mon Sep 17 00:00:00 2001 From: Carlos Rincon <“carlos.r@affinidi.com”> Date: Tue, 14 Jan 2025 22:34:11 -0500 Subject: [PATCH 2/5] feat: encrypted key tests --- .../auth_provider/lib/src/auth_provider.dart | 2 +- .../test/auth_provider_test.dart | 62 ++--- .../auth_provider/test/jwt_helper_test.dart | 213 ++++++++++-------- .../test/pem/openssl-rsa2048-aes192.pem | 30 +++ .../test/pem/openssl-rsa2048-aes256.pem | 30 +++ 5 files changed, 217 insertions(+), 120 deletions(-) create mode 100644 packages/dart/auth_provider/test/pem/openssl-rsa2048-aes192.pem create mode 100644 packages/dart/auth_provider/test/pem/openssl-rsa2048-aes256.pem diff --git a/packages/dart/auth_provider/lib/src/auth_provider.dart b/packages/dart/auth_provider/lib/src/auth_provider.dart index 6cc2315d6..7213e433b 100644 --- a/packages/dart/auth_provider/lib/src/auth_provider.dart +++ b/packages/dart/auth_provider/lib/src/auth_provider.dart @@ -129,7 +129,7 @@ class AuthProvider { additionalPayload: { 'project_id': projectId, 'iota_configuration_id': iotaConfigId, - 'iota_session_id': iotaSessionId, + 'iota_session_id': sessionId, 'scope': 'iota_channel', }, ), diff --git a/packages/dart/auth_provider/test/auth_provider_test.dart b/packages/dart/auth_provider/test/auth_provider_test.dart index 213a696d0..3314046c0 100644 --- a/packages/dart/auth_provider/test/auth_provider_test.dart +++ b/packages/dart/auth_provider/test/auth_provider_test.dart @@ -1,46 +1,24 @@ import 'dart:io'; import 'package:path/path.dart' as path; import 'package:test/test.dart'; +import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; import 'package:affinidi_tdk_auth_provider/affinidi_tdk_auth_provider.dart'; -String loadPemFile(String filename) { - final testDir = Directory.current.path; - final pemPath = path.join(testDir, 'test', 'pem', filename); - return File(pemPath).readAsStringSync(); -} - void main() { - group('AuthProvider Tests', () { + group('Auth Provider Tests', () { final mockProjectId = 'test-project-id'; final mockTokenId = 'test-token-id'; - - late String opensslPrivateKey; - late String opensslPublicKey; - late String sshPrivateKey; - late String sshPublicKey; - late AuthProvider authProvider; setUp(() { - opensslPrivateKey = loadPemFile('1-openssl-private-key.pem'); - opensslPublicKey = loadPemFile('1-openssl-public-key.pem'); - sshPrivateKey = loadPemFile('3-ssh-private-key.pem'); - sshPublicKey = loadPemFile('3-ssh-public-key.pem'); - + final testDir = Directory.current.path; + final keyOpensslRSA2048 = + File(path.join(testDir, 'test', 'pem', 'openssl-rsa2048.pem')) + .readAsStringSync(); authProvider = AuthProvider( projectId: mockProjectId, tokenId: mockTokenId, - privateKey: opensslPrivateKey); - }); - - test('loads OpenSSL key pair successfully', () { - expect(opensslPrivateKey, contains('BEGIN PRIVATE KEY')); - expect(opensslPublicKey, contains('BEGIN PUBLIC KEY')); - }); - - test('loads SSH key pair successfully', () { - expect(sshPrivateKey, contains('BEGIN OPENSSH PRIVATE KEY')); - expect(sshPublicKey, contains('BEGIN RSA PUBLIC KEY')); + privateKey: keyOpensslRSA2048); }); test('createIotaToken returns valid token and session', () { @@ -53,6 +31,19 @@ void main() { expect(result.iotaSessionId, equals(sessionId)); expect(result.iotaJwt, isNotEmpty); + + final jwt = JWT.decode(result.iotaJwt); + expect(jwt.payload['aud'], equals(did)); + expect(jwt.payload['iss'], equals('token/$mockTokenId')); + expect(jwt.payload['sub'], equals('token/$mockTokenId')); + expect(jwt.payload['jti'], isNotEmpty); + expect(jwt.payload['iat'], isA()); + expect(jwt.payload['exp'], isA()); + expect(jwt.payload['exp'] - jwt.payload['iat'], equals(5 * 60)); + expect(jwt.payload['project_id'], equals(mockProjectId)); + expect(jwt.payload['iota_configuration_id'], equals(iotaConfigId)); + expect(jwt.payload['iota_session_id'], equals(sessionId)); + expect(jwt.payload['scope'], equals('iota_channel')); }); test('createIotaToken generates session ID if not provided', () { @@ -64,6 +55,19 @@ void main() { expect(result.iotaSessionId, isNotEmpty); expect(result.iotaJwt, isNotEmpty); + + final jwt = JWT.decode(result.iotaJwt); + expect(jwt.payload['aud'], equals(did)); + expect(jwt.payload['iss'], equals('token/$mockTokenId')); + expect(jwt.payload['sub'], equals('token/$mockTokenId')); + expect(jwt.payload['jti'], isNotEmpty); + expect(jwt.payload['iat'], isA()); + expect(jwt.payload['exp'], isA()); + expect(jwt.payload['exp'] - jwt.payload['iat'], equals(5 * 60)); + expect(jwt.payload['project_id'], equals(mockProjectId)); + expect(jwt.payload['iota_configuration_id'], equals(iotaConfigId)); + expect(jwt.payload['iota_session_id'], equals(result.iotaSessionId)); + expect(jwt.payload['scope'], equals('iota_channel')); }); }); } diff --git a/packages/dart/auth_provider/test/jwt_helper_test.dart b/packages/dart/auth_provider/test/jwt_helper_test.dart index 0513e941c..9ae051563 100644 --- a/packages/dart/auth_provider/test/jwt_helper_test.dart +++ b/packages/dart/auth_provider/test/jwt_helper_test.dart @@ -8,117 +8,92 @@ import 'package:mocktail/mocktail.dart'; class MockIamClient extends Mock implements IamClient {} +void validateJWTClaims(String token, String audience, String tokenId) { + final jwt = JWT.decode(token); + + expect(jwt.payload['aud'], equals(audience)); + expect(jwt.payload['iss'], equals(tokenId)); + expect(jwt.payload['sub'], equals(tokenId)); + expect(jwt.payload['jti'], isNotEmpty); + expect(jwt.payload['iat'], isA()); + expect(jwt.payload['exp'], isA()); + expect(jwt.payload['exp'] - jwt.payload['iat'], equals(5 * 60)); + + expect(jwt.header!['alg'], equals('RS256')); +} + void main() { group('JWTHelper Tests', () { - late IamClient iamClient; - late String keyOpensslRSA2048; - late String keyOpensslRSA4096; - late String keySshRSA2048; - late String keySshRSA4096; - - setUp(() { - final testDir = Directory.current.path; - keyOpensslRSA2048 = - File(path.join(testDir, 'test', 'pem', 'openssl-rsa2048.pem')) - .readAsStringSync(); - keyOpensslRSA4096 = - File(path.join(testDir, 'test', 'pem', 'openssl-rsa4096.pem')) - .readAsStringSync(); - keySshRSA2048 = File(path.join(testDir, 'test', 'pem', 'ssh-rsa2048.pem')) - .readAsStringSync(); - keySshRSA4096 = File(path.join(testDir, 'test', 'pem', 'ssh-rsa4096.pem')) - .readAsStringSync(); - }); + final mockAudience = "test-audience"; + final mockTokenId = "test-token"; + group('signPayload with unencrypted private keys', () { + late String keyOpensslRsa2048; + late String keyOpensslRsa4096; + late String keySshRsa2048; + late String keySshRsa4096; - group('signPayload with unencrypted key', () { - test('creates and signs valid JWT from OpenSSL RSA 2048 key', () { + setUp(() { + final testDir = Directory.current.path; + keyOpensslRsa2048 = + File(path.join(testDir, 'test', 'pem', 'openssl-rsa2048.pem')) + .readAsStringSync(); + keyOpensslRsa4096 = + File(path.join(testDir, 'test', 'pem', 'openssl-rsa4096.pem')) + .readAsStringSync(); + keySshRsa2048 = + File(path.join(testDir, 'test', 'pem', 'ssh-rsa2048.pem')) + .readAsStringSync(); + keySshRsa4096 = + File(path.join(testDir, 'test', 'pem', 'ssh-rsa4096.pem')) + .readAsStringSync(); + }); + test('creates JWT from OpenSSL RSA 2048 key', () { final token = JWTHelper.signPayload( - audience: 'test-audience', - tokenId: 'test-token', - privateKey: keyOpensslRSA2048, + audience: mockAudience, + tokenId: mockTokenId, + privateKey: keyOpensslRsa2048, ); - expect(token, isNotEmpty); - - final jwt = JWT.decode(token); - - expect(jwt.payload['aud'], equals('test-audience')); - expect(jwt.payload['iss'], equals('test-token')); - expect(jwt.payload['sub'], equals('test-token')); - expect(jwt.payload['jti'], isNotEmpty); - expect(jwt.payload['iat'], isA()); - expect(jwt.payload['exp'], isA()); - expect(jwt.payload['exp'] - jwt.payload['iat'], equals(5 * 60)); + validateJWTClaims(token, mockAudience, mockTokenId); }); - test('creates and signs valid JWT from OpenSSL RSA 4096 key', () { + test('creates JWT from OpenSSL RSA 4096 key', () { final token = JWTHelper.signPayload( audience: 'test-audience', tokenId: 'test-token', - privateKey: keyOpensslRSA4096, + privateKey: keyOpensslRsa4096, ); - expect(token, isNotEmpty); - - final jwt = JWT.decode(token); - - expect(jwt.payload['aud'], equals('test-audience')); - expect(jwt.payload['iss'], equals('test-token')); - expect(jwt.payload['sub'], equals('test-token')); - expect(jwt.payload['jti'], isNotEmpty); - expect(jwt.payload['iat'], isA()); - expect(jwt.payload['exp'], isA()); - expect(jwt.payload['exp'] - jwt.payload['iat'], equals(5 * 60)); + validateJWTClaims(token, mockAudience, mockTokenId); }); - test('creates and signs valid JWT from ssh-keygen RSA 2048 key', () { + test('creates JWT from ssh-keygen RSA 2048 key', () { final token = JWTHelper.signPayload( audience: 'test-audience', tokenId: 'test-token', - privateKey: keySshRSA2048, + privateKey: keySshRsa2048, ); - expect(token, isNotEmpty); + validateJWTClaims(token, mockAudience, mockTokenId); + }, skip: 'ssh-keygen key tags are not yet supported'); - final jwt = JWT.decode(token); - - expect(jwt.payload['aud'], equals('test-audience')); - expect(jwt.payload['iss'], equals('test-token')); - expect(jwt.payload['sub'], equals('test-token')); - expect(jwt.payload['jti'], isNotEmpty); - expect(jwt.payload['iat'], isA()); - expect(jwt.payload['exp'], isA()); - expect(jwt.payload['exp'] - jwt.payload['iat'], equals(5 * 60)); - }); - - test('creates and signs valid JWT from ssh-keygen RSA 4096 key', () { + test('creates JWT from ssh-keygen RSA 4096 key', () { final token = JWTHelper.signPayload( audience: 'test-audience', tokenId: 'test-token', - privateKey: keySshRSA4096, + privateKey: keySshRsa4096, ); - expect(token, isNotEmpty); - - final jwt = JWT.decode(token); - - expect(jwt.payload['aud'], equals('test-audience')); - expect(jwt.payload['iss'], equals('test-token')); - expect(jwt.payload['sub'], equals('test-token')); - expect(jwt.payload['jti'], isNotEmpty); - expect(jwt.payload['iat'], isA()); - expect(jwt.payload['exp'], isA()); - expect(jwt.payload['exp'] - jwt.payload['iat'], equals(5 * 60)); - }); + validateJWTClaims(token, mockAudience, mockTokenId); + }, skip: 'ssh-keygen key tags are not yet supported'); test('includes keyId in header when provided', () { final token = JWTHelper.signPayload( audience: 'test-audience', tokenId: 'test-token', - privateKey: keyOpensslRSA2048, + privateKey: keyOpensslRsa2048, keyId: 'test-key-id', ); - final jwt = JWT.decode(token); expect(jwt.header!['kid'], equals('test-key-id')); }); @@ -127,7 +102,7 @@ void main() { final token = JWTHelper.signPayload( audience: 'test-audience', tokenId: 'test-token', - privateKey: keyOpensslRSA2048, + privateKey: keyOpensslRsa2048, additionalPayload: { 'custom_claim': 'custom_value', 'nested_claim': {'key': 'value'} @@ -139,17 +114,6 @@ void main() { expect(jwt.payload['nested_claim'], equals({'key': 'value'})); }); - test('uses RS256 algorithm', () { - final token = JWTHelper.signPayload( - audience: 'test-audience', - tokenId: 'test-token', - privateKey: keyOpensslRSA2048, - ); - - final jwt = JWT.decode(token); - expect(jwt.header!['alg'], equals('RS256')); - }); - test('throws error with invalid private key', () { expect( () => JWTHelper.signPayload( @@ -161,7 +125,76 @@ void main() { }); }); + group('signPayload with encrypted private keys', () { + final String passphrase = 'hello'; + late String keyOpensslRsa2048Aes128; + late String keyOpensslRsa2048Aes192; + late String keyOpensslRsa2048Aes256; + late String keySshRsa4096Encrypted; + + setUp(() { + final testDir = Directory.current.path; + keyOpensslRsa2048Aes128 = File( + path.join(testDir, 'test', 'pem', 'openssl-rsa2048-aes128.pem')) + .readAsStringSync(); + keyOpensslRsa2048Aes192 = File( + path.join(testDir, 'test', 'pem', 'openssl-rsa2048-aes192.pem')) + .readAsStringSync(); + keyOpensslRsa2048Aes256 = File( + path.join(testDir, 'test', 'pem', 'openssl-rsa2048-aes256.pem')) + .readAsStringSync(); + keySshRsa4096Encrypted = + File(path.join(testDir, 'test', 'pem', 'ssh-rsa4096-encrypted.pem')) + .readAsStringSync(); + }); + + test('creates JWT from OpenSSL AES-128 encrypted RSA 2048 key', () { + final token = JWTHelper.signPayload( + audience: 'test-audience', + tokenId: 'test-token', + privateKey: keyOpensslRsa2048Aes128, + passphrase: passphrase, + ); + expect(token, isNotEmpty); + validateJWTClaims(token, mockAudience, mockTokenId); + }, skip: 'encrypted keys are not yet supported'); + + test('creates JWT from OpenSSL AES-192 encrypted RSA 2048 key', () { + final token = JWTHelper.signPayload( + audience: 'test-audience', + tokenId: 'test-token', + privateKey: keyOpensslRsa2048Aes192, + passphrase: passphrase, + ); + expect(token, isNotEmpty); + validateJWTClaims(token, mockAudience, mockTokenId); + }, skip: 'encrypted keys are not yet supported'); + + test('creates JWT from OpenSSL AES-256 encrypted RSA 2048 key', () { + final token = JWTHelper.signPayload( + audience: 'test-audience', + tokenId: 'test-token', + privateKey: keyOpensslRsa2048Aes256, + passphrase: passphrase, + ); + expect(token, isNotEmpty); + validateJWTClaims(token, mockAudience, mockTokenId); + }, skip: 'encrypted keys are not yet supported'); + + test('creates JWT from ssh-keygen encrypted RSA 4096 key', () { + final token = JWTHelper.signPayload( + audience: 'test-audience', + tokenId: 'test-token', + privateKey: keySshRsa4096Encrypted, + passphrase: passphrase); + expect(token, isNotEmpty); + validateJWTClaims(token, mockAudience, mockTokenId); + }, skip: 'ssh-keygen key tags are not yet supported'); + }); + group('fetchPublicKey', () { + late IamClient iamClient; + setUp(() { iamClient = MockIamClient(); }); diff --git a/packages/dart/auth_provider/test/pem/openssl-rsa2048-aes192.pem b/packages/dart/auth_provider/test/pem/openssl-rsa2048-aes192.pem new file mode 100644 index 000000000..4d65291a7 --- /dev/null +++ b/packages/dart/auth_provider/test/pem/openssl-rsa2048-aes192.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQoix8SJM/RJRNGt+C +vHlZpgICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEARYEEPZ44hE6ScmgmsA5 +qLU8pegEggTQiXzvOZKVWu/1Jz6gEgL+QyX1nFjJdWA9tZbphlPis57vdDaWDjOP +yxv2nvCMlWR3SwhcvzVm6tOqkv1Tbrn87kHMzndI7BzgRQx/e9/bEn0K4xOtgXvZ +qKav3jcBbxD7fn0Pt7BdbTI/IcY9biL7UuGcZzh3iWMc1+rf6CErCpn509ijQIL/ +7LlsAOJ0geAMxu8rDPnawq3ZpuWT0hMOwcwhQb/+A9a5QaxS7KOY/1PTHR7DcGlA +3HD9VGxr+59g9xNVr1rHAnmtfUY4JwMU0QEiH2QDdFaRTwDdJwwAHhiihLpK3Row +Vl2E+fCVAc3xYkxsgR1PbZ9jEYTaGakWMQL96UWO5NA2Gnpro716KVWahegPWL/6 +ykPiuA8AlFd3pfMRq4LxnZgJV/xqAX3ooYOMQFR+n2Y5/NujJZkwI7CDovatMm5E +893Fm0bJ+nVYdgSXTfIYfcVlGkqv1Pudu3L1Qhuqaz+pOLsDSAxxrahWU6iMCqWD +GgFJU+7XQX6t+qfLo14V9u3SWKc/L7l40chHQ0dtjdxUJd3o+R1D6j3QM1ydLGZX +IrocgU5M/rLnCnGNUhzd1a0ExWtErnPLW7D2l2EJurZBndY1jEcPhrmi8LZCvJ0a +pGezd7FgSOeXzNt/+BUFZTAAgF+hvcSyFhOQErI4Rg3H5j4s4PPIrUzt6EDp84mc +53s7QYuNHm0ZLx7yIgTcMJfYQyFY7Tm+U7oA3sMBHlHhnk0+D6RxeuXsiwzpoCMC +xiCZ+zV2Yplhd1fkEQ8jtIX6NlXkvDszVoOSPKCXsTkUp+yLP9DKrsc2uSkiS/Ik +1Z8IjdZPFqLHPLUW9Z98tNdD6T1MEK6Qs9sdMXM4ittUeArpMMtpRxxmqWbbpcLX +W52TKuG2yEkS6gdxLy+tBs833oPTyCFvCpAJOHGDzJA0Q4DfJ/3UmRrRdVZx8WTJ +AK7c7awXn8lQIhOnZ8HYi2K95XYJ3FA30+Jr8R0mjhm2XzVqxCG5ULYNpwJ2IEO3 +imDTP2XX26gFSmVa7Q5LrMG/SLUU3jOAHxgdJK1oi+cqpgWgjkorMORwgTFnZOQq +CkazpHSsvrq5Cbk7MyP5nfw9ZZRUPyHdygeSRaW2h6pMp6p/sa9TnJMEsTaLjele +Hze/OAfHkVt8y4sJhVhVf1d8QIsqi+UxyY30/A4gWU3nc0+37VzePz2A4MVOYIwu +8CTNtsDP853o6rzJj/EqoMhQgEztXEXdkaSrgiUHgjcoW3w+cPEmv3apJsJCU5sF +Ai/RjqZ5ni+cxd/a74kvCMSFeEHV5wk+uT8uklLVK+EQ2oJHagd5DvLw5sLy52Bi +ZQ/g7OUF5H9JUihJWQpmS4SV6JRsoY/WNL+eGopjs3PCp5zcI3ijckmkgsOPgsLL +be8aQFfhMMHb3CTD1klxvq1htH0Qn4Hlc+hlUVffQ1khRVY0I3+xrZN/1R0ZebOT +IZTxziU8gRk2N77OHQOKBjlmo0bLAK06g+gGEjZuGyq11KsmhnshAatj9VXUeas3 +3gXQcMEJ1qjX8BWDnRxsDghVbQ2f1RVZpyXQbObOHNISuysJNZ69sDLeglfVWpb/ +E+ozVuqJpapmhyJWLJKdLdHS+gDOm2T9c1xAn4uTErGjwqa8Nof8u9Y= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/packages/dart/auth_provider/test/pem/openssl-rsa2048-aes256.pem b/packages/dart/auth_provider/test/pem/openssl-rsa2048-aes256.pem new file mode 100644 index 000000000..11ec5f0d8 --- /dev/null +++ b/packages/dart/auth_provider/test/pem/openssl-rsa2048-aes256.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQEn45uhBQPIPUnD2U +Dvm7fgICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEDLTRNVvkduIUTiG +WmNIqIkEggTQ7D3WyMsJW+DGUIeO82C4p22NwEeqUoEopTKSRhWnNSHZV5ln/5BU +jLuK6KxwLE6Ia70LekLl6CrvN+6V84RkAH7caNO9DZKeEC8HQHPdEeZJq+wx7M3Q +8hMfxA6F03id11toHw5HC3Iu382uZmEoroXQVqXJFLSyQ+hmoOJVXKh1+TgAtW5E +VHtn6xk+HWsi5Ux1duBLDEc4Q49jWSkEJMfKHLqcZmalKYnT2RCGVB0pRvqy62Xx +PJG8sH8/dTfZ+0mgGRRn6EO9GMfbCDzsmdshfwvxGyBUkTUTmpcfSFeOjOAu6XQj +FoytPG4ECrZQdWHxD8glCa2ROyZuzPSuu6QRSA5a8A1lnBAnnEcg+ecIzissEdDc +T5n3vDvQry0pAQb+DXirijnZ1JshD8kOlafdmO9bHwG8O522QwxSc8pN16hfmfKD +XUBJB6JJ/l6o+81CdcJGe5rgrF36/Xn5D+dVTIosVXwT3JyEU2pXlBDlLWaqAsVU +JsMHhn/HA/SC/ClXUgc4n8QpvIQWG1udzU5LnXn78phoXsDAOh80KtT9hiettUT7 +hZ9N2sDnFZHh7oQXlR3Bkd9pK7wvfdl8ixorFYDNI8B6ojV9PuHPc4NSGa616QoA +D7wG7YSqR/6k/VwQwG9IRlS7ntlEUxOz910sOdLaZaAXqrwxPlicg4X0gbIqIUK2 +6tw5JB9vH3WKmxYTtTbqCPXZ6SMy6126gfL/ebTx9R17e7PvVRqfxPCnH1LvrtYi +CIyre439kknh5huJ0VMyOJmIChgTB2vo8PmkQ6qfEK8Sy4hSji0r/TEk2kAApelT +PIf5avZ+2FdAMK2qu/6oiGzWshjrJRDLQrbuRZkNtfVcW6t3BM9f/pIJB8RLWGTK +HcjCidCBVbIWJv8gOPZkGsbNI6TmE6aZWuaG1rg2zoSNH8iN6qvr+NgKWgWa8fEq +LRR1hnVek6WFbaSlMG1Zj2ZSANaCV0nFSwc5K5PLmqgfmLHYOZL3ybbUEi5L/1g7 +iT6YQXx8k0Galb5R64UjlaVbOnYU6Qqgla2pcA2zz2gzzT5vSiIep45rkdmDRyeU +rEQuowx0xTbBWqoh4/cPaWo+Q1sSBk0U9C2IFhJFKEF3mMVZfWMmyp8jTDMLAQ8N +kl8pVZNY/vuHL6gHPlvEJ2xUnfuyhOPM17bsU0UhX/+jxMZ2X16L1flPCr4QGNpK +Avq/t5vl2R5VEBDZ7AdoqL/truopCs6ZYlH4rF57HP+BuJEywgSs0xGfWRtxwpj0 ++N2UCkuFq10jYN1jRWGe6+ey9XJXjyhXsxI6LTyHCduySlzVzWYv6ValdaflhHRm +GO9gqB3ECyFjOYA5kwD1//JTRlkS78JO8K9mEbYTgCaAkBguJGPVXEFUNb8xCPew +Fl08wcOf01rz9r4nh10/T7KHGNy48gcWCyWIwrNc/f45kVg9ywUdapbdFjQyVMVf +SaqXCQEObHiqlW4H44mjwvVCXCh6SdfLdViEXXreWPveantkUDy3I2ILzD0Pyj+3 +Z1YLfhHORM2RCacNc6GM+R9TBrAFSfDMrNtekPs/MtAcrBJyDadZdbvoWQCuxGUR +qXpxPclzXq/L4DEjxKusSE3d8N3Sq+L/OzSNUeG25tIfG1Ut2MjYAg4= +-----END ENCRYPTED PRIVATE KEY----- From cb527d26f540a3026a3a3d7a6a3812e548953974 Mon Sep 17 00:00:00 2001 From: Carlos Rincon <“carlos.r@affinidi.com”> Date: Tue, 14 Jan 2025 22:45:01 -0500 Subject: [PATCH 3/5] chore: test group name --- packages/dart/auth_provider/test/jwt_helper_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dart/auth_provider/test/jwt_helper_test.dart b/packages/dart/auth_provider/test/jwt_helper_test.dart index 9ae051563..2a1035446 100644 --- a/packages/dart/auth_provider/test/jwt_helper_test.dart +++ b/packages/dart/auth_provider/test/jwt_helper_test.dart @@ -192,7 +192,7 @@ void main() { }, skip: 'ssh-keygen key tags are not yet supported'); }); - group('fetchPublicKey', () { + group('Affinidi Elements Public Key', () { late IamClient iamClient; setUp(() { From 05ff93422c802c253528db4f8b72c281b7a17022 Mon Sep 17 00:00:00 2001 From: Carlos Rincon <“carlos.r@affinidi.com”> Date: Tue, 14 Jan 2025 22:56:59 -0500 Subject: [PATCH 4/5] chore: use mock vars --- .../auth_provider/test/jwt_helper_test.dart | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/dart/auth_provider/test/jwt_helper_test.dart b/packages/dart/auth_provider/test/jwt_helper_test.dart index 2a1035446..f95372724 100644 --- a/packages/dart/auth_provider/test/jwt_helper_test.dart +++ b/packages/dart/auth_provider/test/jwt_helper_test.dart @@ -59,8 +59,8 @@ void main() { test('creates JWT from OpenSSL RSA 4096 key', () { final token = JWTHelper.signPayload( - audience: 'test-audience', - tokenId: 'test-token', + audience: mockAudience, + tokenId: mockTokenId, privateKey: keyOpensslRsa4096, ); expect(token, isNotEmpty); @@ -69,8 +69,8 @@ void main() { test('creates JWT from ssh-keygen RSA 2048 key', () { final token = JWTHelper.signPayload( - audience: 'test-audience', - tokenId: 'test-token', + audience: mockAudience, + tokenId: mockTokenId, privateKey: keySshRsa2048, ); expect(token, isNotEmpty); @@ -79,8 +79,8 @@ void main() { test('creates JWT from ssh-keygen RSA 4096 key', () { final token = JWTHelper.signPayload( - audience: 'test-audience', - tokenId: 'test-token', + audience: mockAudience, + tokenId: mockTokenId, privateKey: keySshRsa4096, ); expect(token, isNotEmpty); @@ -89,8 +89,8 @@ void main() { test('includes keyId in header when provided', () { final token = JWTHelper.signPayload( - audience: 'test-audience', - tokenId: 'test-token', + audience: mockAudience, + tokenId: mockTokenId, privateKey: keyOpensslRsa2048, keyId: 'test-key-id', ); @@ -100,8 +100,8 @@ void main() { test('includes additional payload when provided', () { final token = JWTHelper.signPayload( - audience: 'test-audience', - tokenId: 'test-token', + audience: mockAudience, + tokenId: mockTokenId, privateKey: keyOpensslRsa2048, additionalPayload: { 'custom_claim': 'custom_value', @@ -117,8 +117,8 @@ void main() { test('throws error with invalid private key', () { expect( () => JWTHelper.signPayload( - audience: 'test-audience', - tokenId: 'test-token', + audience: mockAudience, + tokenId: mockTokenId, privateKey: 'invalid-key', ), throwsA(isA())); @@ -150,8 +150,8 @@ void main() { test('creates JWT from OpenSSL AES-128 encrypted RSA 2048 key', () { final token = JWTHelper.signPayload( - audience: 'test-audience', - tokenId: 'test-token', + audience: mockAudience, + tokenId: mockTokenId, privateKey: keyOpensslRsa2048Aes128, passphrase: passphrase, ); @@ -161,8 +161,8 @@ void main() { test('creates JWT from OpenSSL AES-192 encrypted RSA 2048 key', () { final token = JWTHelper.signPayload( - audience: 'test-audience', - tokenId: 'test-token', + audience: mockAudience, + tokenId: mockTokenId, privateKey: keyOpensslRsa2048Aes192, passphrase: passphrase, ); @@ -172,8 +172,8 @@ void main() { test('creates JWT from OpenSSL AES-256 encrypted RSA 2048 key', () { final token = JWTHelper.signPayload( - audience: 'test-audience', - tokenId: 'test-token', + audience: mockAudience, + tokenId: mockTokenId, privateKey: keyOpensslRsa2048Aes256, passphrase: passphrase, ); @@ -183,8 +183,8 @@ void main() { test('creates JWT from ssh-keygen encrypted RSA 4096 key', () { final token = JWTHelper.signPayload( - audience: 'test-audience', - tokenId: 'test-token', + audience: mockAudience, + tokenId: mockTokenId, privateKey: keySshRsa4096Encrypted, passphrase: passphrase); expect(token, isNotEmpty); From 7e1899f93f0a8230bb601d549c14eab89e5d7a8b Mon Sep 17 00:00:00 2001 From: Carlos Rincon <“carlos.r@affinidi.com”> Date: Wed, 15 Jan 2025 12:05:46 -0500 Subject: [PATCH 5/5] fix: type and var fixes --- .../auth_provider/lib/src/auth_provider.dart | 7 +++--- .../auth_provider/lib/src/iam_client.dart | 10 ++++++-- .../auth_provider/lib/src/jwt_helper.dart | 23 ++++++++----------- .../auth_provider/test/jwt_helper_test.dart | 18 ++++++--------- tests/integration/dart/test/environment.dart | 9 ++++---- 5 files changed, 33 insertions(+), 34 deletions(-) diff --git a/packages/dart/auth_provider/lib/src/auth_provider.dart b/packages/dart/auth_provider/lib/src/auth_provider.dart index 7213e433b..c7373eb90 100644 --- a/packages/dart/auth_provider/lib/src/auth_provider.dart +++ b/packages/dart/auth_provider/lib/src/auth_provider.dart @@ -1,11 +1,10 @@ import 'dart:convert'; - -import 'package:affinidi_tdk_auth_provider/src/iam_client.dart'; import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; -import 'package:affinidi_tdk_auth_provider/src/jwt_helper.dart'; import 'package:affinidi_tdk_common/affinidi_tdk_common.dart'; import 'package:http/http.dart' as http; import 'package:uuid/uuid.dart'; +import 'iam_client.dart'; +import 'jwt_helper.dart'; class AuthProvider { final String projectId; @@ -42,7 +41,7 @@ class AuthProvider { return true; } - IamClient iamClient = IamClient(apiGatewayUrl: apiGatewayUrl); + final iamClient = IamClient(apiGatewayUrl: apiGatewayUrl); publicKey ??= await JWTHelper.fetchPublicKey(iamClient); try { JWT.verify(projectScopedToken!, publicKey!); diff --git a/packages/dart/auth_provider/lib/src/iam_client.dart b/packages/dart/auth_provider/lib/src/iam_client.dart index bbf9f20eb..20253a2b5 100644 --- a/packages/dart/auth_provider/lib/src/iam_client.dart +++ b/packages/dart/auth_provider/lib/src/iam_client.dart @@ -9,7 +9,7 @@ class IamClient { final http.Client _httpClient; final String _apiGatewayUrl; - Future getPublicKeyJWKS() async { + Future> getPublicKeyJWKS() async { final response = await _httpClient.get( Uri.parse('$_apiGatewayUrl/iam/.well-known/jwks.json'), headers: {'Content-Type': 'application/json'}, @@ -19,6 +19,12 @@ class IamClient { throw Exception('Failed to fetch public key'); } - return jsonDecode(response.body); + final data = jsonDecode(response.body); + + if (data['keys'] == null || data['keys'].isEmpty) { + throw Exception('No keys found in JWKS'); + } + + return data['keys'][0]; } } diff --git a/packages/dart/auth_provider/lib/src/jwt_helper.dart b/packages/dart/auth_provider/lib/src/jwt_helper.dart index 29239c062..2a99d3dd5 100644 --- a/packages/dart/auth_provider/lib/src/jwt_helper.dart +++ b/packages/dart/auth_provider/lib/src/jwt_helper.dart @@ -1,9 +1,9 @@ -import 'package:affinidi_tdk_auth_provider/src/iam_client.dart'; +import 'dart:convert'; +import 'dart:typed_data'; import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; import 'package:uuid/uuid.dart'; -import 'dart:convert'; import 'package:pointycastle/export.dart' as pce; -import 'dart:typed_data'; +import 'iam_client.dart'; class JWTHelper { static String signPayload({ @@ -42,14 +42,7 @@ class JWTHelper { } static Future fetchPublicKey(IamClient iamClient) async { - final data = await iamClient.getPublicKeyJWKS(); - - if (data['keys'] == null || data['keys'].isEmpty) { - throw Exception('No keys found in JWKS'); - } - - final jwk = data['keys'][0]; - + final jwk = await iamClient.getPublicKeyJWKS(); return ECPublicKey.raw(_jwkToPublicKey(jwk)); } @@ -58,9 +51,13 @@ class JWTHelper { throw UnimplementedError('Unsupported algorithm or key type'); } + if (jwk['x'] == null || jwk['y'] == null) { + throw Exception('Invalid public key'); + } + // Decode base64url-encoded x and y coordinates - final Uint8List x = base64Url.decode(_addPadding(jwk['x'])); - final Uint8List y = base64Url.decode(_addPadding(jwk['y'])); + final Uint8List x = base64Url.decode(_addPadding(jwk['x']!)); + final Uint8List y = base64Url.decode(_addPadding(jwk['y']!)); // Create the EC domain parameters for P-256 (secp256r1) final curve = pce.ECCurve_secp256r1(); diff --git a/packages/dart/auth_provider/test/jwt_helper_test.dart b/packages/dart/auth_provider/test/jwt_helper_test.dart index f95372724..5cefe701a 100644 --- a/packages/dart/auth_provider/test/jwt_helper_test.dart +++ b/packages/dart/auth_provider/test/jwt_helper_test.dart @@ -201,17 +201,13 @@ void main() { test('successfully parses JWKS', () async { final mockPublicKeyJWKS = { - 'keys': [ - { - 'kid': 'a622a999-9846-48cf-a470-22759e1f435a', - 'alg': 'ES256', - 'use': 'sig', - 'kty': 'EC', - 'crv': 'P-256', - 'x': 'b3kdYEBrlWjQwY55F8MhXC97pwkjTpcQZZ09oDDBK4c', - 'y': 'wlopQwIPWuT55M3ZfCDZdoBs1nh2kwEvzPjnkakf96U' - } - ] + 'kid': 'a622a999-9846-48cf-a470-22759e1f435a', + 'alg': 'ES256', + 'use': 'sig', + 'kty': 'EC', + 'crv': 'P-256', + 'x': 'b3kdYEBrlWjQwY55F8MhXC97pwkjTpcQZZ09oDDBK4c', + 'y': 'wlopQwIPWuT55M3ZfCDZdoBs1nh2kwEvzPjnkakf96U' }; when(() => iamClient.getPublicKeyJWKS()).thenAnswer( diff --git a/tests/integration/dart/test/environment.dart b/tests/integration/dart/test/environment.dart index 71c143605..92e3aa5a0 100644 --- a/tests/integration/dart/test/environment.dart +++ b/tests/integration/dart/test/environment.dart @@ -17,18 +17,19 @@ class ProjectEnvironment { } ProjectEnvironment getProjectEnvironment() { - final env = DotEnv(includePlatformEnvironment: true)..load(['../../.env']); + final env = DotEnv()..load(['../../.env']); if (!env.isEveryDefined(['PROJECT_ID', 'TOKEN_ID', 'PRIVATE_KEY'])) { - throw ('Missing environment variables. Please provide PROJECT_ID, TOKEN_ID and PRIVATE_KEY'); + throw Exception( + 'Missing environment variables. Please provide PROJECT_ID, TOKEN_ID and PRIVATE_KEY'); } // Workaround for dotenv multiline limitations final privateKey = env['PRIVATE_KEY']!.replaceAll('\\n', '\n'); final token = env['TOKEN_ID']!; final projectId = env['PROJECT_ID']!; - final keyId = env['KEY_ID']; - final passphrase = env['PASSPHRASE']; + final keyId = env['KEY_ID'] ?? ''; + final passphrase = env['PASSPHRASE'] ?? ''; return ProjectEnvironment( projectId: projectId,