From 40f4cbc7e552e8c37fec2b1791af8ede6762582f Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sat, 26 Apr 2025 14:59:47 +0900 Subject: [PATCH 01/36] Add ngsiv2 client --- pom.xml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e235bcd..6c050f8 100644 --- a/pom.xml +++ b/pom.xml @@ -3,6 +3,7 @@ 4.0.0 + city.makeour moc @@ -18,6 +19,13 @@ 1.8 + + + jitpack.io + https://jitpack.io + + + junit @@ -30,6 +38,11 @@ aws-sdk-java 2.31.20 + + com.github.makeOurCity + ngsiv2-java + v0.0.1 + @@ -77,4 +90,4 @@ - + \ No newline at end of file From 892f0cc640a83bc8c16c24699edc38ea74067e49 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sat, 26 Apr 2025 15:48:41 +0900 Subject: [PATCH 02/36] Add client --- src/main/java/city/makeour/moc/MocClient.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/java/city/makeour/moc/MocClient.java diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java new file mode 100644 index 0000000..68045f2 --- /dev/null +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -0,0 +1,44 @@ +package city.makeour.moc; + +import city.makeour.ngsi.v2.api.ApiEntryPointApi; +import city.makeour.ngsi.v2.invoker.ApiClient; +import city.makeour.ngsi.v2.invoker.ApiException; +import city.makeour.ngsi.v2.invoker.Configuration; +import city.makeour.ngsi.v2.model.RetrieveApiResourcesResponse; + +public class MocClient { + private String baseUrl; + private String accessToken; + private String refreshToken; + + public MocClient(String baseUrl) throws ApiException { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + // Configure clients using the `defaultClient` object, such as + // overriding the host and port, timeout, etc. + ApiEntryPointApi apiInstance = new ApiEntryPointApi(defaultClient); + + RetrieveApiResourcesResponse result = apiInstance.retrieveAPIResources(); + System.out.println(result); + this.baseUrl = baseUrl; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public String getBaseUrl() { + return baseUrl; + } + + public String getAccessToken() { + return accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } +} From 55e6bf5c5328771c5ea6cec1f4be23621492e122 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sat, 26 Apr 2025 16:45:20 +0900 Subject: [PATCH 03/36] test passed --- pom.xml | 19 ++- src/main/java/city/makeour/moc/MocClient.java | 23 ++-- .../java/city/makeour/moc/MocClientTest.java | 112 ++++++++++++++++++ 3 files changed, 142 insertions(+), 12 deletions(-) create mode 100644 src/test/java/city/makeour/moc/MocClientTest.java diff --git a/pom.xml b/pom.xml index 6c050f8..1f4a340 100644 --- a/pom.xml +++ b/pom.xml @@ -28,11 +28,17 @@ - junit - junit - 4.11 + org.junit.jupiter + junit-jupiter + 5.9.2 test + + org.mockito + mockito-junit-jupiter + 5.3.1 + test + software.amazon.awssdk aws-sdk-java @@ -64,7 +70,12 @@ maven-surefire-plugin - 2.22.1 + 2.22.2 + + + **/*Test.java + + maven-jar-plugin diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index 68045f2..39c1541 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -1,25 +1,32 @@ package city.makeour.moc; import city.makeour.ngsi.v2.api.ApiEntryPointApi; -import city.makeour.ngsi.v2.invoker.ApiClient; import city.makeour.ngsi.v2.invoker.ApiException; import city.makeour.ngsi.v2.invoker.Configuration; import city.makeour.ngsi.v2.model.RetrieveApiResourcesResponse; public class MocClient { - private String baseUrl; + private final String baseUrl; private String accessToken; private String refreshToken; + private final ApiEntryPointApi apiEntryPoint; public MocClient(String baseUrl) throws ApiException { - ApiClient defaultClient = Configuration.getDefaultApiClient(); - // Configure clients using the `defaultClient` object, such as - // overriding the host and port, timeout, etc. - ApiEntryPointApi apiInstance = new ApiEntryPointApi(defaultClient); + this(baseUrl, new ApiEntryPointApi(Configuration.getDefaultApiClient())); + } - RetrieveApiResourcesResponse result = apiInstance.retrieveAPIResources(); - System.out.println(result); + // テスト用のコンストラクタ + MocClient(String baseUrl, ApiEntryPointApi apiEntryPoint) throws ApiException { this.baseUrl = baseUrl; + this.apiEntryPoint = apiEntryPoint; + validateConnection(); + } + + private void validateConnection() throws ApiException { + RetrieveApiResourcesResponse result = apiEntryPoint.retrieveAPIResources(); + if (result == null) { + throw new ApiException("Failed to retrieve API resources"); + } } public void setAccessToken(String accessToken) { diff --git a/src/test/java/city/makeour/moc/MocClientTest.java b/src/test/java/city/makeour/moc/MocClientTest.java new file mode 100644 index 0000000..fdfb4e3 --- /dev/null +++ b/src/test/java/city/makeour/moc/MocClientTest.java @@ -0,0 +1,112 @@ +package city.makeour.moc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import city.makeour.ngsi.v2.api.ApiEntryPointApi; +import city.makeour.ngsi.v2.invoker.ApiException; +import city.makeour.ngsi.v2.model.RetrieveApiResourcesResponse; + +@ExtendWith(MockitoExtension.class) +@DisplayName("MocClient Tests") +class MocClientTest { + private static final String BASE_URL = "http://example.com"; + + @Mock + private ApiEntryPointApi mockApiEntryPoint; + + @Mock + private RetrieveApiResourcesResponse mockResponse; + + private MocClient client; + + @BeforeEach + void setUp() throws ApiException { + when(mockApiEntryPoint.retrieveAPIResources()).thenReturn(mockResponse); + client = new MocClient(BASE_URL, mockApiEntryPoint); + } + + @Nested + @DisplayName("基本的な初期化テスト") + class InitializationTests { + @Test + @DisplayName("baseURLが正しく設定されていること") + void testGetBaseUrl() { + assertEquals(BASE_URL, client.getBaseUrl(), "baseURLが正しく設定されていない"); + } + + @Test + @DisplayName("初期状態ではトークンがnullであること") + void testInitialTokensAreNull() { + assertNull(client.getAccessToken(), "初期状態でアクセストークンはnullのはず"); + assertNull(client.getRefreshToken(), "初期状態でリフレッシュトークンはnullのはず"); + } + + @Test + @DisplayName("APIリソース取得の検証が行われること") + void testApiResourceValidation() throws ApiException { + verify(mockApiEntryPoint, times(1)).retrieveAPIResources(); + } + } + + @Nested + @DisplayName("トークン操作テスト") + class TokenOperationTests { + @Test + @DisplayName("アクセストークンの設定と取得") + void testSetAndGetAccessToken() { + String accessToken = "test-access-token"; + client.setAccessToken(accessToken); + assertEquals(accessToken, client.getAccessToken(), "設定したアクセストークンと取得したトークンが一致しない"); + } + + @Test + @DisplayName("リフレッシュトークンの設定と取得") + void testSetAndGetRefreshToken() { + String refreshToken = "test-refresh-token"; + client.setRefreshToken(refreshToken); + assertEquals(refreshToken, client.getRefreshToken(), "設定したリフレッシュトークンと取得したトークンが一致しない"); + } + } + + @Nested + @DisplayName("エラーケーステスト") + class ErrorTests { + @Test + @DisplayName("APIリソース取得失敗時に例外が発生すること") + void testApiResourceFailure() throws ApiException { + when(mockApiEntryPoint.retrieveAPIResources()).thenReturn(null); + + ApiException exception = assertThrows(ApiException.class, + () -> new MocClient(BASE_URL, mockApiEntryPoint), + "APIリソース取得失敗時にApiExceptionがスローされるべき"); + + assertEquals("Failed to retrieve API resources", exception.getMessage()); + } + + @Test + @DisplayName("API呼び出しエラー時に例外が発生すること") + void testApiCallError() throws ApiException { + ApiException expectedException = new ApiException("API call failed"); + when(mockApiEntryPoint.retrieveAPIResources()).thenThrow(expectedException); + + ApiException exception = assertThrows(ApiException.class, + () -> new MocClient(BASE_URL, mockApiEntryPoint), + "API呼び出しエラー時にApiExceptionがスローされるべき"); + + assertEquals(expectedException, exception); + } + } +} \ No newline at end of file From c95efd0bdd04f5be3bd5a5ab1abb656820fd78c7 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sat, 26 Apr 2025 18:20:08 +0900 Subject: [PATCH 04/36] Add set fiware service method --- src/main/java/city/makeour/moc/MocClient.java | 60 ++++++++++++++++++- .../java/city/makeour/moc/MocClientTest.java | 58 +++++++++++++++++- 2 files changed, 113 insertions(+), 5 deletions(-) diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index 39c1541..1a26186 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -1,24 +1,38 @@ package city.makeour.moc; +import java.util.List; + import city.makeour.ngsi.v2.api.ApiEntryPointApi; +import city.makeour.ngsi.v2.api.EntitiesApi; +import city.makeour.ngsi.v2.invoker.ApiClient; import city.makeour.ngsi.v2.invoker.ApiException; import city.makeour.ngsi.v2.invoker.Configuration; +import city.makeour.ngsi.v2.model.ListEntitiesResponse; import city.makeour.ngsi.v2.model.RetrieveApiResourcesResponse; public class MocClient { private final String baseUrl; private String accessToken; private String refreshToken; + private String fiwareService; + private final ApiClient apiClient; private final ApiEntryPointApi apiEntryPoint; + private final EntitiesApi entitiesApi; public MocClient(String baseUrl) throws ApiException { - this(baseUrl, new ApiEntryPointApi(Configuration.getDefaultApiClient())); + this.baseUrl = baseUrl; + this.apiClient = Configuration.getDefaultApiClient(); + this.apiEntryPoint = new ApiEntryPointApi(apiClient); + this.entitiesApi = new EntitiesApi(apiClient); + validateConnection(); } // テスト用のコンストラクタ - MocClient(String baseUrl, ApiEntryPointApi apiEntryPoint) throws ApiException { + MocClient(String baseUrl, ApiEntryPointApi apiEntryPoint, EntitiesApi entitiesApi) throws ApiException { this.baseUrl = baseUrl; + this.apiClient = Configuration.getDefaultApiClient(); this.apiEntryPoint = apiEntryPoint; + this.entitiesApi = entitiesApi; validateConnection(); } @@ -48,4 +62,46 @@ public String getAccessToken() { public String getRefreshToken() { return refreshToken; } + + /** + * Fiware-Serviceヘッダの値を設定します + * + * @param fiwareService テナント名 + */ + public void setFiwareService(String fiwareService) { + this.fiwareService = fiwareService; + // TODO: ApiClientの正しいヘッダー設定方法を調査する + } + + /** + * 設定されているFiware-Serviceヘッダの値を取得します + * + * @return テナント名 + */ + public String getFiwareService() { + return fiwareService; + } + + /** + * エンティティの一覧を取得します + * + * @return エンティティのリスト + * @throws ApiException API呼び出しが失敗した場合 + */ + public List listEntities() throws ApiException { + return entitiesApi.listEntities(null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null); + } + + /** + * エンティティの一覧をページ指定で取得します + * + * @param offset オフセット値 + * @return エンティティのリスト + * @throws ApiException API呼び出しが失敗した場合 + */ + public List listEntities(Double offset) throws ApiException { + return entitiesApi.listEntities(null, null, null, null, null, null, null, null, + null, null, offset, null, null, null, null); + } } diff --git a/src/test/java/city/makeour/moc/MocClientTest.java b/src/test/java/city/makeour/moc/MocClientTest.java index fdfb4e3..5534ad2 100644 --- a/src/test/java/city/makeour/moc/MocClientTest.java +++ b/src/test/java/city/makeour/moc/MocClientTest.java @@ -7,6 +7,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.Arrays; +import java.util.List; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -16,7 +19,9 @@ import org.mockito.junit.jupiter.MockitoExtension; import city.makeour.ngsi.v2.api.ApiEntryPointApi; +import city.makeour.ngsi.v2.api.EntitiesApi; import city.makeour.ngsi.v2.invoker.ApiException; +import city.makeour.ngsi.v2.model.ListEntitiesResponse; import city.makeour.ngsi.v2.model.RetrieveApiResourcesResponse; @ExtendWith(MockitoExtension.class) @@ -27,6 +32,9 @@ class MocClientTest { @Mock private ApiEntryPointApi mockApiEntryPoint; + @Mock + private EntitiesApi mockEntitiesApi; + @Mock private RetrieveApiResourcesResponse mockResponse; @@ -35,7 +43,7 @@ class MocClientTest { @BeforeEach void setUp() throws ApiException { when(mockApiEntryPoint.retrieveAPIResources()).thenReturn(mockResponse); - client = new MocClient(BASE_URL, mockApiEntryPoint); + client = new MocClient(BASE_URL, mockApiEntryPoint, mockEntitiesApi); } @Nested @@ -90,7 +98,7 @@ void testApiResourceFailure() throws ApiException { when(mockApiEntryPoint.retrieveAPIResources()).thenReturn(null); ApiException exception = assertThrows(ApiException.class, - () -> new MocClient(BASE_URL, mockApiEntryPoint), + () -> new MocClient(BASE_URL, mockApiEntryPoint, mockEntitiesApi), "APIリソース取得失敗時にApiExceptionがスローされるべき"); assertEquals("Failed to retrieve API resources", exception.getMessage()); @@ -103,10 +111,54 @@ void testApiCallError() throws ApiException { when(mockApiEntryPoint.retrieveAPIResources()).thenThrow(expectedException); ApiException exception = assertThrows(ApiException.class, - () -> new MocClient(BASE_URL, mockApiEntryPoint), + () -> new MocClient(BASE_URL, mockApiEntryPoint, mockEntitiesApi), "API呼び出しエラー時にApiExceptionがスローされるべき"); assertEquals(expectedException, exception); } } + + @Nested + @DisplayName("エンティティ操作テスト") + class EntityOperationTests { + @Test + @DisplayName("エンティティ一覧の取得") + void testListEntities() throws ApiException { + // テストデータの準備 + ListEntitiesResponse entity1 = new ListEntitiesResponse().id("entity1").type("testType"); + ListEntitiesResponse entity2 = new ListEntitiesResponse().id("entity2").type("testType"); + List expectedEntities = Arrays.asList(entity1, entity2); + + // モックの設定 + when(mockEntitiesApi.listEntities(null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null)) + .thenReturn(expectedEntities); + + // テスト実行 + List actualEntities = client.listEntities(); + + // 検証 + assertEquals(expectedEntities, actualEntities, "取得したエンティティリストが期待値と一致しない"); + verify(mockEntitiesApi, times(1)) + .listEntities(null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null); + } + + @Test + @DisplayName("エンティティ一覧取得時のエラー処理") + void testListEntitiesError() throws ApiException { + // モックの設定 + ApiException expectedException = new ApiException("Failed to list entities"); + when(mockEntitiesApi.listEntities(null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null)) + .thenThrow(expectedException); + + // テスト実行と検証 + ApiException exception = assertThrows(ApiException.class, + () -> client.listEntities(), + "エンティティ一覧取得失敗時にApiExceptionがスローされるべき"); + + assertEquals(expectedException, exception); + } + } } \ No newline at end of file From ee2fcf0197d1fc288af3ade67a8733c8038d6137 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sat, 26 Apr 2025 19:12:46 +0900 Subject: [PATCH 05/36] using apiClient --- src/main/java/city/makeour/moc/MocClient.java | 9 +++++---- src/test/java/city/makeour/moc/MocClientTest.java | 10 +++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index 1a26186..312b707 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -6,7 +6,6 @@ import city.makeour.ngsi.v2.api.EntitiesApi; import city.makeour.ngsi.v2.invoker.ApiClient; import city.makeour.ngsi.v2.invoker.ApiException; -import city.makeour.ngsi.v2.invoker.Configuration; import city.makeour.ngsi.v2.model.ListEntitiesResponse; import city.makeour.ngsi.v2.model.RetrieveApiResourcesResponse; @@ -21,16 +20,18 @@ public class MocClient { public MocClient(String baseUrl) throws ApiException { this.baseUrl = baseUrl; - this.apiClient = Configuration.getDefaultApiClient(); + this.apiClient = new ApiClient(); + this.apiClient.setBasePath(baseUrl); this.apiEntryPoint = new ApiEntryPointApi(apiClient); this.entitiesApi = new EntitiesApi(apiClient); validateConnection(); } // テスト用のコンストラクタ - MocClient(String baseUrl, ApiEntryPointApi apiEntryPoint, EntitiesApi entitiesApi) throws ApiException { + MocClient(String baseUrl, ApiClient apiClient, ApiEntryPointApi apiEntryPoint, EntitiesApi entitiesApi) + throws ApiException { this.baseUrl = baseUrl; - this.apiClient = Configuration.getDefaultApiClient(); + this.apiClient = apiClient; this.apiEntryPoint = apiEntryPoint; this.entitiesApi = entitiesApi; validateConnection(); diff --git a/src/test/java/city/makeour/moc/MocClientTest.java b/src/test/java/city/makeour/moc/MocClientTest.java index 5534ad2..786f923 100644 --- a/src/test/java/city/makeour/moc/MocClientTest.java +++ b/src/test/java/city/makeour/moc/MocClientTest.java @@ -20,6 +20,7 @@ import city.makeour.ngsi.v2.api.ApiEntryPointApi; import city.makeour.ngsi.v2.api.EntitiesApi; +import city.makeour.ngsi.v2.invoker.ApiClient; import city.makeour.ngsi.v2.invoker.ApiException; import city.makeour.ngsi.v2.model.ListEntitiesResponse; import city.makeour.ngsi.v2.model.RetrieveApiResourcesResponse; @@ -29,6 +30,9 @@ class MocClientTest { private static final String BASE_URL = "http://example.com"; + @Mock + private ApiClient mockApiClient; + @Mock private ApiEntryPointApi mockApiEntryPoint; @@ -43,7 +47,7 @@ class MocClientTest { @BeforeEach void setUp() throws ApiException { when(mockApiEntryPoint.retrieveAPIResources()).thenReturn(mockResponse); - client = new MocClient(BASE_URL, mockApiEntryPoint, mockEntitiesApi); + client = new MocClient(BASE_URL, mockApiClient, mockApiEntryPoint, mockEntitiesApi); } @Nested @@ -98,7 +102,7 @@ void testApiResourceFailure() throws ApiException { when(mockApiEntryPoint.retrieveAPIResources()).thenReturn(null); ApiException exception = assertThrows(ApiException.class, - () -> new MocClient(BASE_URL, mockApiEntryPoint, mockEntitiesApi), + () -> new MocClient(BASE_URL, mockApiClient, mockApiEntryPoint, mockEntitiesApi), "APIリソース取得失敗時にApiExceptionがスローされるべき"); assertEquals("Failed to retrieve API resources", exception.getMessage()); @@ -111,7 +115,7 @@ void testApiCallError() throws ApiException { when(mockApiEntryPoint.retrieveAPIResources()).thenThrow(expectedException); ApiException exception = assertThrows(ApiException.class, - () -> new MocClient(BASE_URL, mockApiEntryPoint, mockEntitiesApi), + () -> new MocClient(BASE_URL, mockApiClient, mockApiEntryPoint, mockEntitiesApi), "API呼び出しエラー時にApiExceptionがスローされるべき"); assertEquals(expectedException, exception); From 3c53a802309e93b26b5343b060c467951cb63476 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sat, 26 Apr 2025 19:18:45 +0900 Subject: [PATCH 06/36] set base path and host --- src/main/java/city/makeour/moc/MocClient.java | 13 ++++--------- src/test/java/city/makeour/moc/MocClientTest.java | 6 ------ 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index 312b707..d0deb92 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -10,7 +10,6 @@ import city.makeour.ngsi.v2.model.RetrieveApiResourcesResponse; public class MocClient { - private final String baseUrl; private String accessToken; private String refreshToken; private String fiwareService; @@ -18,10 +17,10 @@ public class MocClient { private final ApiEntryPointApi apiEntryPoint; private final EntitiesApi entitiesApi; - public MocClient(String baseUrl) throws ApiException { - this.baseUrl = baseUrl; + public MocClient(String host, String basePath) throws ApiException { this.apiClient = new ApiClient(); - this.apiClient.setBasePath(baseUrl); + this.apiClient.setHost(host); + this.apiClient.setBasePath(basePath); this.apiEntryPoint = new ApiEntryPointApi(apiClient); this.entitiesApi = new EntitiesApi(apiClient); validateConnection(); @@ -30,7 +29,7 @@ public MocClient(String baseUrl) throws ApiException { // テスト用のコンストラクタ MocClient(String baseUrl, ApiClient apiClient, ApiEntryPointApi apiEntryPoint, EntitiesApi entitiesApi) throws ApiException { - this.baseUrl = baseUrl; + apiClient.setBasePath(baseUrl); this.apiClient = apiClient; this.apiEntryPoint = apiEntryPoint; this.entitiesApi = entitiesApi; @@ -52,10 +51,6 @@ public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; } - public String getBaseUrl() { - return baseUrl; - } - public String getAccessToken() { return accessToken; } diff --git a/src/test/java/city/makeour/moc/MocClientTest.java b/src/test/java/city/makeour/moc/MocClientTest.java index 786f923..21f7764 100644 --- a/src/test/java/city/makeour/moc/MocClientTest.java +++ b/src/test/java/city/makeour/moc/MocClientTest.java @@ -53,12 +53,6 @@ void setUp() throws ApiException { @Nested @DisplayName("基本的な初期化テスト") class InitializationTests { - @Test - @DisplayName("baseURLが正しく設定されていること") - void testGetBaseUrl() { - assertEquals(BASE_URL, client.getBaseUrl(), "baseURLが正しく設定されていない"); - } - @Test @DisplayName("初期状態ではトークンがnullであること") void testInitialTokensAreNull() { From c96dec44641fe48d3ba57196e319e3804c4fcabd Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sat, 26 Apr 2025 20:58:54 +0900 Subject: [PATCH 07/36] multi tenant not now --- src/main/java/city/makeour/moc/MocClient.java | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index d0deb92..9708282 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -12,7 +12,6 @@ public class MocClient { private String accessToken; private String refreshToken; - private String fiwareService; private final ApiClient apiClient; private final ApiEntryPointApi apiEntryPoint; private final EntitiesApi entitiesApi; @@ -65,17 +64,7 @@ public String getRefreshToken() { * @param fiwareService テナント名 */ public void setFiwareService(String fiwareService) { - this.fiwareService = fiwareService; - // TODO: ApiClientの正しいヘッダー設定方法を調査する - } - - /** - * 設定されているFiware-Serviceヘッダの値を取得します - * - * @return テナント名 - */ - public String getFiwareService() { - return fiwareService; + throw new UnsupportedOperationException("Fiware-Service header is not supported"); } /** From 069dc92509273e1c9102cbadf57b345a5886f327 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sat, 26 Apr 2025 22:06:18 +0900 Subject: [PATCH 08/36] test works --- pom.xml | 18 +++++++++++++++--- src/main/java/city/makeour/moc/MocClient.java | 4 ++++ .../java/city/makeour/moc/MocClientTest.java | 13 +++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 1f4a340..bf02ecd 100644 --- a/pom.xml +++ b/pom.xml @@ -15,8 +15,8 @@ UTF-8 - 1.8 - 1.8 + 17 + 17 @@ -33,6 +33,18 @@ 5.9.2 test + + org.junit.jupiter + junit-jupiter-api + 5.10.1 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.10.1 + test + org.mockito mockito-junit-jupiter @@ -70,7 +82,7 @@ maven-surefire-plugin - 2.22.2 + 3.5.3 **/*Test.java diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index 9708282..858a1b4 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -16,6 +16,10 @@ public class MocClient { private final ApiEntryPointApi apiEntryPoint; private final EntitiesApi entitiesApi; + public MocClient(String host) throws ApiException { + this(host, "/v2"); + } + public MocClient(String host, String basePath) throws ApiException { this.apiClient = new ApiClient(); this.apiClient.setHost(host); diff --git a/src/test/java/city/makeour/moc/MocClientTest.java b/src/test/java/city/makeour/moc/MocClientTest.java index 21f7764..9df8e5e 100644 --- a/src/test/java/city/makeour/moc/MocClientTest.java +++ b/src/test/java/city/makeour/moc/MocClientTest.java @@ -116,6 +116,19 @@ void testApiCallError() throws ApiException { } } + @Nested + @DisplayName("手動実装テスト") + class ManualOperationTests { + @Test + @DisplayName("Fiware-Serviceヘッダの設定") + void testSetFiwareService() throws ApiException { + MocClient client = new MocClient("orion.sandbox.makeour.city"); + + System.out.println(client.listEntities()); + assertEquals("hoge", "fuga"); + } + } + @Nested @DisplayName("エンティティ操作テスト") class EntityOperationTests { From 1dc1f905cbd96562e56397b1629769291fbeccf4 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sun, 27 Apr 2025 20:04:07 +0900 Subject: [PATCH 09/36] add new constructo --- src/main/java/city/makeour/moc/MocClient.java | 15 +++++++++------ src/test/java/city/makeour/moc/MocClientTest.java | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index 858a1b4..06d444a 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -16,14 +16,13 @@ public class MocClient { private final ApiEntryPointApi apiEntryPoint; private final EntitiesApi entitiesApi; - public MocClient(String host) throws ApiException { - this(host, "/v2"); + public MocClient() throws ApiException { + this("https://orion.sandbox.makeour.city/v2"); } - public MocClient(String host, String basePath) throws ApiException { + public MocClient(String baseUri) throws ApiException { this.apiClient = new ApiClient(); - this.apiClient.setHost(host); - this.apiClient.setBasePath(basePath); + this.apiClient.updateBaseUri(baseUri); this.apiEntryPoint = new ApiEntryPointApi(apiClient); this.entitiesApi = new EntitiesApi(apiClient); validateConnection(); @@ -32,13 +31,17 @@ public MocClient(String host, String basePath) throws ApiException { // テスト用のコンストラクタ MocClient(String baseUrl, ApiClient apiClient, ApiEntryPointApi apiEntryPoint, EntitiesApi entitiesApi) throws ApiException { - apiClient.setBasePath(baseUrl); + apiClient.updateBaseUri(baseUrl); this.apiClient = apiClient; this.apiEntryPoint = apiEntryPoint; this.entitiesApi = entitiesApi; validateConnection(); } + public void updateBaseUri(String baseUrl) { + apiClient.updateBaseUri(baseUrl); + } + private void validateConnection() throws ApiException { RetrieveApiResourcesResponse result = apiEntryPoint.retrieveAPIResources(); if (result == null) { diff --git a/src/test/java/city/makeour/moc/MocClientTest.java b/src/test/java/city/makeour/moc/MocClientTest.java index 9df8e5e..c65cf4b 100644 --- a/src/test/java/city/makeour/moc/MocClientTest.java +++ b/src/test/java/city/makeour/moc/MocClientTest.java @@ -122,7 +122,7 @@ class ManualOperationTests { @Test @DisplayName("Fiware-Serviceヘッダの設定") void testSetFiwareService() throws ApiException { - MocClient client = new MocClient("orion.sandbox.makeour.city"); + MocClient client = new MocClient(); System.out.println(client.listEntities()); assertEquals("hoge", "fuga"); From b5f32d23ac3be2c9603d1e2f2e6f94a67e9fe28c Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sun, 27 Apr 2025 20:07:10 +0900 Subject: [PATCH 10/36] update readme --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bf02ecd..882a5e8 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ com.github.makeOurCity ngsiv2-java - v0.0.1 + 0.0.2 From 360e8ea009c0df10b26af6d31a8c3c42b3e099b7 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sun, 27 Apr 2025 20:39:51 +0900 Subject: [PATCH 11/36] add client --- src/main/java/city/makeour/moc/MocClient.java | 94 +--------- .../java/city/makeour/moc/MocClientTest.java | 175 ------------------ 2 files changed, 10 insertions(+), 259 deletions(-) delete mode 100644 src/test/java/city/makeour/moc/MocClientTest.java diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index 06d444a..37a69bf 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -1,99 +1,25 @@ package city.makeour.moc; -import java.util.List; - -import city.makeour.ngsi.v2.api.ApiEntryPointApi; import city.makeour.ngsi.v2.api.EntitiesApi; import city.makeour.ngsi.v2.invoker.ApiClient; -import city.makeour.ngsi.v2.invoker.ApiException; -import city.makeour.ngsi.v2.model.ListEntitiesResponse; -import city.makeour.ngsi.v2.model.RetrieveApiResourcesResponse; public class MocClient { - private String accessToken; - private String refreshToken; - private final ApiClient apiClient; - private final ApiEntryPointApi apiEntryPoint; - private final EntitiesApi entitiesApi; - - public MocClient() throws ApiException { - this("https://orion.sandbox.makeour.city/v2"); - } - - public MocClient(String baseUri) throws ApiException { - this.apiClient = new ApiClient(); - this.apiClient.updateBaseUri(baseUri); - this.apiEntryPoint = new ApiEntryPointApi(apiClient); - this.entitiesApi = new EntitiesApi(apiClient); - validateConnection(); - } + protected ApiClient apiClient; - // テスト用のコンストラクタ - MocClient(String baseUrl, ApiClient apiClient, ApiEntryPointApi apiEntryPoint, EntitiesApi entitiesApi) - throws ApiException { - apiClient.updateBaseUri(baseUrl); - this.apiClient = apiClient; - this.apiEntryPoint = apiEntryPoint; - this.entitiesApi = entitiesApi; - validateConnection(); - } + protected EntitiesApi entitiesApi; - public void updateBaseUri(String baseUrl) { - apiClient.updateBaseUri(baseUrl); + public MocClient() { + this("https://orion.sandbox.makeour.city"); } - private void validateConnection() throws ApiException { - RetrieveApiResourcesResponse result = apiEntryPoint.retrieveAPIResources(); - if (result == null) { - throw new ApiException("Failed to retrieve API resources"); - } - } - - public void setAccessToken(String accessToken) { - this.accessToken = accessToken; - } - - public void setRefreshToken(String refreshToken) { - this.refreshToken = refreshToken; - } - - public String getAccessToken() { - return accessToken; - } - - public String getRefreshToken() { - return refreshToken; - } - - /** - * Fiware-Serviceヘッダの値を設定します - * - * @param fiwareService テナント名 - */ - public void setFiwareService(String fiwareService) { - throw new UnsupportedOperationException("Fiware-Service header is not supported"); - } + public MocClient(String basePath) { + this.apiClient = new ApiClient(); + this.apiClient.setBasePath(basePath); - /** - * エンティティの一覧を取得します - * - * @return エンティティのリスト - * @throws ApiException API呼び出しが失敗した場合 - */ - public List listEntities() throws ApiException { - return entitiesApi.listEntities(null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null); + this.entitiesApi = new EntitiesApi(this.apiClient); } - /** - * エンティティの一覧をページ指定で取得します - * - * @param offset オフセット値 - * @return エンティティのリスト - * @throws ApiException API呼び出しが失敗した場合 - */ - public List listEntities(Double offset) throws ApiException { - return entitiesApi.listEntities(null, null, null, null, null, null, null, null, - null, null, offset, null, null, null, null); + public EntitiesApi entities() { + return this.entitiesApi; } } diff --git a/src/test/java/city/makeour/moc/MocClientTest.java b/src/test/java/city/makeour/moc/MocClientTest.java deleted file mode 100644 index c65cf4b..0000000 --- a/src/test/java/city/makeour/moc/MocClientTest.java +++ /dev/null @@ -1,175 +0,0 @@ -package city.makeour.moc; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import city.makeour.ngsi.v2.api.ApiEntryPointApi; -import city.makeour.ngsi.v2.api.EntitiesApi; -import city.makeour.ngsi.v2.invoker.ApiClient; -import city.makeour.ngsi.v2.invoker.ApiException; -import city.makeour.ngsi.v2.model.ListEntitiesResponse; -import city.makeour.ngsi.v2.model.RetrieveApiResourcesResponse; - -@ExtendWith(MockitoExtension.class) -@DisplayName("MocClient Tests") -class MocClientTest { - private static final String BASE_URL = "http://example.com"; - - @Mock - private ApiClient mockApiClient; - - @Mock - private ApiEntryPointApi mockApiEntryPoint; - - @Mock - private EntitiesApi mockEntitiesApi; - - @Mock - private RetrieveApiResourcesResponse mockResponse; - - private MocClient client; - - @BeforeEach - void setUp() throws ApiException { - when(mockApiEntryPoint.retrieveAPIResources()).thenReturn(mockResponse); - client = new MocClient(BASE_URL, mockApiClient, mockApiEntryPoint, mockEntitiesApi); - } - - @Nested - @DisplayName("基本的な初期化テスト") - class InitializationTests { - @Test - @DisplayName("初期状態ではトークンがnullであること") - void testInitialTokensAreNull() { - assertNull(client.getAccessToken(), "初期状態でアクセストークンはnullのはず"); - assertNull(client.getRefreshToken(), "初期状態でリフレッシュトークンはnullのはず"); - } - - @Test - @DisplayName("APIリソース取得の検証が行われること") - void testApiResourceValidation() throws ApiException { - verify(mockApiEntryPoint, times(1)).retrieveAPIResources(); - } - } - - @Nested - @DisplayName("トークン操作テスト") - class TokenOperationTests { - @Test - @DisplayName("アクセストークンの設定と取得") - void testSetAndGetAccessToken() { - String accessToken = "test-access-token"; - client.setAccessToken(accessToken); - assertEquals(accessToken, client.getAccessToken(), "設定したアクセストークンと取得したトークンが一致しない"); - } - - @Test - @DisplayName("リフレッシュトークンの設定と取得") - void testSetAndGetRefreshToken() { - String refreshToken = "test-refresh-token"; - client.setRefreshToken(refreshToken); - assertEquals(refreshToken, client.getRefreshToken(), "設定したリフレッシュトークンと取得したトークンが一致しない"); - } - } - - @Nested - @DisplayName("エラーケーステスト") - class ErrorTests { - @Test - @DisplayName("APIリソース取得失敗時に例外が発生すること") - void testApiResourceFailure() throws ApiException { - when(mockApiEntryPoint.retrieveAPIResources()).thenReturn(null); - - ApiException exception = assertThrows(ApiException.class, - () -> new MocClient(BASE_URL, mockApiClient, mockApiEntryPoint, mockEntitiesApi), - "APIリソース取得失敗時にApiExceptionがスローされるべき"); - - assertEquals("Failed to retrieve API resources", exception.getMessage()); - } - - @Test - @DisplayName("API呼び出しエラー時に例外が発生すること") - void testApiCallError() throws ApiException { - ApiException expectedException = new ApiException("API call failed"); - when(mockApiEntryPoint.retrieveAPIResources()).thenThrow(expectedException); - - ApiException exception = assertThrows(ApiException.class, - () -> new MocClient(BASE_URL, mockApiClient, mockApiEntryPoint, mockEntitiesApi), - "API呼び出しエラー時にApiExceptionがスローされるべき"); - - assertEquals(expectedException, exception); - } - } - - @Nested - @DisplayName("手動実装テスト") - class ManualOperationTests { - @Test - @DisplayName("Fiware-Serviceヘッダの設定") - void testSetFiwareService() throws ApiException { - MocClient client = new MocClient(); - - System.out.println(client.listEntities()); - assertEquals("hoge", "fuga"); - } - } - - @Nested - @DisplayName("エンティティ操作テスト") - class EntityOperationTests { - @Test - @DisplayName("エンティティ一覧の取得") - void testListEntities() throws ApiException { - // テストデータの準備 - ListEntitiesResponse entity1 = new ListEntitiesResponse().id("entity1").type("testType"); - ListEntitiesResponse entity2 = new ListEntitiesResponse().id("entity2").type("testType"); - List expectedEntities = Arrays.asList(entity1, entity2); - - // モックの設定 - when(mockEntitiesApi.listEntities(null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null)) - .thenReturn(expectedEntities); - - // テスト実行 - List actualEntities = client.listEntities(); - - // 検証 - assertEquals(expectedEntities, actualEntities, "取得したエンティティリストが期待値と一致しない"); - verify(mockEntitiesApi, times(1)) - .listEntities(null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null); - } - - @Test - @DisplayName("エンティティ一覧取得時のエラー処理") - void testListEntitiesError() throws ApiException { - // モックの設定 - ApiException expectedException = new ApiException("Failed to list entities"); - when(mockEntitiesApi.listEntities(null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null)) - .thenThrow(expectedException); - - // テスト実行と検証 - ApiException exception = assertThrows(ApiException.class, - () -> client.listEntities(), - "エンティティ一覧取得失敗時にApiExceptionがスローされるべき"); - - assertEquals(expectedException, exception); - } - } -} \ No newline at end of file From 5dcb5acd3a0926366ec75b69066b5011b382f534 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sun, 27 Apr 2025 20:45:07 +0900 Subject: [PATCH 12/36] add first test --- pom.xml | 15 ++++---- src/main/java/city/makeour/moc/MocClient.java | 32 +++++++++++------ .../java/city/makeour/moc/MocClientTest.java | 34 +++++++++++++++++++ 3 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 src/test/java/city/makeour/moc/MocClientTest.java diff --git a/pom.xml b/pom.xml index 882a5e8..ae80dd8 100644 --- a/pom.xml +++ b/pom.xml @@ -21,8 +21,12 @@ - jitpack.io - https://jitpack.io + central + https://repo.maven.apache.org/maven2 + + + github + https://maven.pkg.github.com/makeOurCity/ngsiv2-java @@ -53,14 +57,9 @@ software.amazon.awssdk - aws-sdk-java + cognitoidentityprovider 2.31.20 - - com.github.makeOurCity - ngsiv2-java - 0.0.2 - diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index 37a69bf..7c02796 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -1,25 +1,35 @@ package city.makeour.moc; -import city.makeour.ngsi.v2.api.EntitiesApi; -import city.makeour.ngsi.v2.invoker.ApiClient; - public class MocClient { - protected ApiClient apiClient; - - protected EntitiesApi entitiesApi; + private final String basePath; + private final HttpClient httpClient; public MocClient() { this("https://orion.sandbox.makeour.city"); } public MocClient(String basePath) { - this.apiClient = new ApiClient(); - this.apiClient.setBasePath(basePath); + this.basePath = basePath; + this.httpClient = createHttpClient(); + } - this.entitiesApi = new EntitiesApi(this.apiClient); + protected HttpClient createHttpClient() { + return new HttpClient(basePath); } - public EntitiesApi entities() { - return this.entitiesApi; + public HttpClient getHttpClient() { + return httpClient; + } + + public static class HttpClient { + private final String basePath; + + public HttpClient(String basePath) { + this.basePath = basePath; + } + + public String getBasePath() { + return basePath; + } } } diff --git a/src/test/java/city/makeour/moc/MocClientTest.java b/src/test/java/city/makeour/moc/MocClientTest.java new file mode 100644 index 0000000..d972f3a --- /dev/null +++ b/src/test/java/city/makeour/moc/MocClientTest.java @@ -0,0 +1,34 @@ +package city.makeour.moc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import city.makeour.moc.MocClient.HttpClient; + +@ExtendWith(MockitoExtension.class) +class MocClientTest { + + @Test + void defaultConstructorShouldSetDefaultBasePath() { + MocClient client = new MocClient(); + assertEquals("https://orion.sandbox.makeour.city", client.getHttpClient().getBasePath()); + } + + @Test + void constructorWithBasePathShouldSetSpecifiedPath() { + String customPath = "https://custom.example.com"; + MocClient client = new MocClient(customPath); + assertEquals(customPath, client.getHttpClient().getBasePath()); + } + + @Test + void createHttpClientShouldCreateNewInstance() { + MocClient client = new MocClient(); + HttpClient httpClient = client.getHttpClient(); + assertNotNull(httpClient); + } +} \ No newline at end of file From 7509b3ccb16ec477b5f43cd7b86b8e653e2351cf Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sun, 27 Apr 2025 20:47:23 +0900 Subject: [PATCH 13/36] Add ci test --- .editorconfig | 6 +++--- .github/workflows/test.yml | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/test.yml diff --git a/.editorconfig b/.editorconfig index b1faa38..865f611 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,11 +5,11 @@ root = true [*] indent_style = space -indent_size = 4 +indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = false insert_final_newline = false -[*.xml] -indent_size = 2 +[*.java] +indent_size = 4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..a9e8668 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,24 @@ +name: Java CI with Maven + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: "17" + distribution: "temurin" + cache: maven + + - name: Run tests with Maven + run: mvn -B test --file pom.xml From 3bb3c604c9a21efaf58fd9d6d122d155931f82b0 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sun, 27 Apr 2025 20:48:54 +0900 Subject: [PATCH 14/36] Add readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..741e3f4 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# MoC Java +MoC client for Java + +[![Java CI with Maven](https://github.com/makeOurCity/moc-java/actions/workflows/test.yml/badge.svg)](https://github.com/makeOurCity/moc-java/actions/workflows/test.yml) \ No newline at end of file From d974934c5fa6dfb28253612bc2da2c9dd2dddc0a Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sun, 27 Apr 2025 20:59:42 +0900 Subject: [PATCH 15/36] Revert "add first test" This reverts commit 5dcb5acd3a0926366ec75b69066b5011b382f534. --- pom.xml | 15 ++++---- src/main/java/city/makeour/moc/MocClient.java | 32 ++++++----------- .../java/city/makeour/moc/MocClientTest.java | 34 ------------------- 3 files changed, 19 insertions(+), 62 deletions(-) delete mode 100644 src/test/java/city/makeour/moc/MocClientTest.java diff --git a/pom.xml b/pom.xml index ae80dd8..882a5e8 100644 --- a/pom.xml +++ b/pom.xml @@ -21,12 +21,8 @@ - central - https://repo.maven.apache.org/maven2 - - - github - https://maven.pkg.github.com/makeOurCity/ngsiv2-java + jitpack.io + https://jitpack.io @@ -57,9 +53,14 @@ software.amazon.awssdk - cognitoidentityprovider + aws-sdk-java 2.31.20 + + com.github.makeOurCity + ngsiv2-java + 0.0.2 + diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index 7c02796..37a69bf 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -1,35 +1,25 @@ package city.makeour.moc; +import city.makeour.ngsi.v2.api.EntitiesApi; +import city.makeour.ngsi.v2.invoker.ApiClient; + public class MocClient { - private final String basePath; - private final HttpClient httpClient; + protected ApiClient apiClient; + + protected EntitiesApi entitiesApi; public MocClient() { this("https://orion.sandbox.makeour.city"); } public MocClient(String basePath) { - this.basePath = basePath; - this.httpClient = createHttpClient(); - } + this.apiClient = new ApiClient(); + this.apiClient.setBasePath(basePath); - protected HttpClient createHttpClient() { - return new HttpClient(basePath); + this.entitiesApi = new EntitiesApi(this.apiClient); } - public HttpClient getHttpClient() { - return httpClient; - } - - public static class HttpClient { - private final String basePath; - - public HttpClient(String basePath) { - this.basePath = basePath; - } - - public String getBasePath() { - return basePath; - } + public EntitiesApi entities() { + return this.entitiesApi; } } diff --git a/src/test/java/city/makeour/moc/MocClientTest.java b/src/test/java/city/makeour/moc/MocClientTest.java deleted file mode 100644 index d972f3a..0000000 --- a/src/test/java/city/makeour/moc/MocClientTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package city.makeour.moc; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; - -import city.makeour.moc.MocClient.HttpClient; - -@ExtendWith(MockitoExtension.class) -class MocClientTest { - - @Test - void defaultConstructorShouldSetDefaultBasePath() { - MocClient client = new MocClient(); - assertEquals("https://orion.sandbox.makeour.city", client.getHttpClient().getBasePath()); - } - - @Test - void constructorWithBasePathShouldSetSpecifiedPath() { - String customPath = "https://custom.example.com"; - MocClient client = new MocClient(customPath); - assertEquals(customPath, client.getHttpClient().getBasePath()); - } - - @Test - void createHttpClientShouldCreateNewInstance() { - MocClient client = new MocClient(); - HttpClient httpClient = client.getHttpClient(); - assertNotNull(httpClient); - } -} \ No newline at end of file From b030bed895fb0de26e5fdd0019203ec761d4e6a7 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sun, 27 Apr 2025 21:04:01 +0900 Subject: [PATCH 16/36] Add test --- .../java/city/makeour/moc/MocClientTest.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/test/java/city/makeour/moc/MocClientTest.java diff --git a/src/test/java/city/makeour/moc/MocClientTest.java b/src/test/java/city/makeour/moc/MocClientTest.java new file mode 100644 index 0000000..914b1db --- /dev/null +++ b/src/test/java/city/makeour/moc/MocClientTest.java @@ -0,0 +1,37 @@ +package city.makeour.moc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import city.makeour.ngsi.v2.api.EntitiesApi; + +class MocClientTest { + + @Test + @DisplayName("デフォルトコンストラクタで正しいベースパスが設定されることを確認") + void defaultConstructorShouldSetCorrectBasePath() { + MocClient client = new MocClient(); + assertEquals("https://orion.sandbox.makeour.city", client.apiClient.getBasePath()); + } + + @Test + @DisplayName("カスタムベースパスが正しく設定されることを確認") + void constructorWithBasePathShouldSetCustomBasePath() { + String customBasePath = "https://custom.orion.example.com"; + MocClient client = new MocClient(customBasePath); + assertEquals(customBasePath, client.apiClient.getBasePath()); + } + + @Test + @DisplayName("entities()メソッドが正しいEntitiesApiインスタンスを返すことを確認") + void entitiesMethodShouldReturnEntitiesApiInstance() { + MocClient client = new MocClient(); + EntitiesApi entitiesApi = client.entities(); + + assertNotNull(entitiesApi); + assertEquals(client.entitiesApi, entitiesApi); + } +} \ No newline at end of file From 3ebf5054f8554b89f1d5a5e4ac54e53f6a8e6c77 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sun, 27 Apr 2025 21:10:39 +0900 Subject: [PATCH 17/36] Add test --- .../java/city/makeour/moc/MocClientTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/city/makeour/moc/MocClientTest.java b/src/test/java/city/makeour/moc/MocClientTest.java index 914b1db..7dc17e8 100644 --- a/src/test/java/city/makeour/moc/MocClientTest.java +++ b/src/test/java/city/makeour/moc/MocClientTest.java @@ -3,10 +3,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import java.util.List; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import city.makeour.ngsi.v2.api.EntitiesApi; +import city.makeour.ngsi.v2.model.ListEntitiesResponse; class MocClientTest { @@ -34,4 +37,19 @@ void entitiesMethodShouldReturnEntitiesApiInstance() { assertNotNull(entitiesApi); assertEquals(client.entitiesApi, entitiesApi); } + + @Test + @DisplayName("entities apiで、entityの一覧を取得するテスト") + void testEntitiesApi() { + MocClient client = new MocClient(); + EntitiesApi entitiesApi = client.entities(); + + List list = entitiesApi.listEntities(null, null, null, null, null, null, null, null, null, + null, null, null, + null, + null, + null); + assertNotNull(list); + System.out.println("Entities: " + list); + } } \ No newline at end of file From 85a0d986ed8ec9fe47c65017c9d7b46008370344 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sun, 27 Apr 2025 21:38:58 +0900 Subject: [PATCH 18/36] Add set token adn more --- .../java/city/makeour/FetchCognitoToken.java | 4 ++-- .../city/makeour/TokenFetcherInterface.java | 7 +++++++ src/main/java/city/makeour/moc/MocClient.java | 21 +++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 src/main/java/city/makeour/TokenFetcherInterface.java diff --git a/src/main/java/city/makeour/FetchCognitoToken.java b/src/main/java/city/makeour/FetchCognitoToken.java index 945bd91..359e514 100644 --- a/src/main/java/city/makeour/FetchCognitoToken.java +++ b/src/main/java/city/makeour/FetchCognitoToken.java @@ -9,7 +9,7 @@ import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthFlowType; import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthenticationResultType; -public class FetchCognitoToken { +public class FetchCognitoToken implements TokenFetcherInterface { protected CognitoIdentityProviderClient client; protected String cognitoUserPoolId; @@ -19,7 +19,7 @@ public class FetchCognitoToken { protected String username; protected String password; - public void construct(String cognitoUserPoolId, String cognitoClientId) { + public FetchCognitoToken(String cognitoUserPoolId, String cognitoClientId) { this.cognitoClientId = cognitoClientId; this.cognitoUserPoolId = cognitoUserPoolId; this.refreshToken = null; diff --git a/src/main/java/city/makeour/TokenFetcherInterface.java b/src/main/java/city/makeour/TokenFetcherInterface.java new file mode 100644 index 0000000..8925cdd --- /dev/null +++ b/src/main/java/city/makeour/TokenFetcherInterface.java @@ -0,0 +1,7 @@ +package city.makeour; + +public interface TokenFetcherInterface { + void setAuthParameters(String username, String password); + + String fetchToken(); +} diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index 37a69bf..fe0a8f4 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -1,5 +1,7 @@ package city.makeour.moc; +import city.makeour.FetchCognitoToken; +import city.makeour.TokenFetcherInterface; import city.makeour.ngsi.v2.api.EntitiesApi; import city.makeour.ngsi.v2.invoker.ApiClient; @@ -8,6 +10,8 @@ public class MocClient { protected EntitiesApi entitiesApi; + protected TokenFetcherInterface tokenFetcher; + public MocClient() { this("https://orion.sandbox.makeour.city"); } @@ -22,4 +26,21 @@ public MocClient(String basePath) { public EntitiesApi entities() { return this.entitiesApi; } + + public void setMocAuthInfo(String cognitoUserPoolId, String cognitoClientId) { + this.tokenFetcher = new FetchCognitoToken(cognitoUserPoolId, cognitoClientId); + } + + public void login(String username, String password) { + if (this.tokenFetcher == null) { + throw new IllegalStateException("MocClient is not initialized with Cognito auth info."); + } + + this.tokenFetcher.setAuthParameters(username, password); + this.setToken(this.tokenFetcher.fetchToken()); + } + + public void setToken(String token) { + this.apiClient.addDefaultHeader("Authorization", token); + } } From 7f9e90e05d775b02bb9805e5dbe84045e349e153 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Mon, 28 Apr 2025 14:09:01 +0900 Subject: [PATCH 19/36] using srp auth --- .../java/city/makeour/FetchCognitoToken.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/main/java/city/makeour/FetchCognitoToken.java b/src/main/java/city/makeour/FetchCognitoToken.java index 359e514..89ab1b7 100644 --- a/src/main/java/city/makeour/FetchCognitoToken.java +++ b/src/main/java/city/makeour/FetchCognitoToken.java @@ -3,11 +3,13 @@ import java.util.HashMap; import java.util.Map; +import software.amazon.awssdk.awscore.defaultsmode.DefaultsMode; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient; -import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminInitiateAuthRequest; -import software.amazon.awssdk.services.cognitoidentityprovider.model.AdminInitiateAuthResponse; import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthFlowType; import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthenticationResultType; +import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthRequest; +import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthResponse; public class FetchCognitoToken implements TokenFetcherInterface { @@ -23,7 +25,10 @@ public FetchCognitoToken(String cognitoUserPoolId, String cognitoClientId) { this.cognitoClientId = cognitoClientId; this.cognitoUserPoolId = cognitoUserPoolId; this.refreshToken = null; - // this.client = CognitoIdentityProviderClient.getInstance(); + this.client = CognitoIdentityProviderClient.builder() + .defaultsMode(DefaultsMode.STANDARD) + .region(Region.AP_NORTHEAST_1) + .build(); } public void setAuthParameters(String username, String password) { @@ -32,32 +37,32 @@ public void setAuthParameters(String username, String password) { } public String fetchToken() { - AdminInitiateAuthRequest authRequest = null; + InitiateAuthRequest authRequest = null; if (this.refreshToken != null) { Map authParameters = new HashMap<>(); authParameters.put("REFRESH_TOKEN", this.refreshToken); - authRequest = AdminInitiateAuthRequest.builder() + authRequest = InitiateAuthRequest.builder() .clientId(this.cognitoClientId) - .userPoolId(this.cognitoUserPoolId) + // .userPoolId(this.cognitoUserPoolId) .authParameters(authParameters) - .authFlow(AuthFlowType.REFRESH_TOKEN_AUTH) + .authFlow(AuthFlowType.USER_SRP_AUTH) .build(); } else { Map authParameters = new HashMap<>(); authParameters.put("USERNAME", username); authParameters.put("PASSWORD", password); - authRequest = AdminInitiateAuthRequest.builder() + authRequest = InitiateAuthRequest.builder() .clientId(this.cognitoClientId) - .userPoolId(this.cognitoUserPoolId) + // .userPoolId(this.cognitoUserPoolId) .authParameters(authParameters) - .authFlow(AuthFlowType.ADMIN_USER_PASSWORD_AUTH) + .authFlow(AuthFlowType.USER_SRP_AUTH) .build(); } - AdminInitiateAuthResponse response = this.client.adminInitiateAuth(authRequest); + InitiateAuthResponse response = this.client.initiateAuth(authRequest); AuthenticationResultType result = response.authenticationResult(); this.refreshToken = result.refreshToken(); From 8366b9a37d091b068ec73b0b1c215f26ee198223 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Mon, 28 Apr 2025 14:29:16 +0900 Subject: [PATCH 20/36] add srp auth progress --- .../java/city/makeour/FetchCognitoToken.java | 145 +++++++++++++++--- 1 file changed, 125 insertions(+), 20 deletions(-) diff --git a/src/main/java/city/makeour/FetchCognitoToken.java b/src/main/java/city/makeour/FetchCognitoToken.java index 89ab1b7..3398e0f 100644 --- a/src/main/java/city/makeour/FetchCognitoToken.java +++ b/src/main/java/city/makeour/FetchCognitoToken.java @@ -1,5 +1,11 @@ package city.makeour; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -10,6 +16,8 @@ import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthenticationResultType; import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthRequest; import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthResponse; +import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeRequest; +import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeResponse; public class FetchCognitoToken implements TokenFetcherInterface { @@ -36,41 +44,138 @@ public void setAuthParameters(String username, String password) { this.password = password; } - public String fetchToken() { - InitiateAuthRequest authRequest = null; + // SRPに必要な定数 + private static final BigInteger N = new BigInteger( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", + 16); + private static final BigInteger g = BigInteger.valueOf(2); + private static final BigInteger k = BigInteger.valueOf(3); + private static final int EPHEMERAL_KEY_LENGTH = 1024; + public String fetchToken() { if (this.refreshToken != null) { + return fetchTokenWithRefresh(); + } + return fetchTokenWithSRP(); + } + + private String fetchTokenWithRefresh() { + Map authParameters = new HashMap<>(); + authParameters.put("REFRESH_TOKEN", this.refreshToken); + + InitiateAuthRequest authRequest = InitiateAuthRequest.builder() + .clientId(this.cognitoClientId) + .authParameters(authParameters) + .authFlow(AuthFlowType.REFRESH_TOKEN_AUTH) + .build(); + + InitiateAuthResponse response = this.client.initiateAuth(authRequest); + AuthenticationResultType result = response.authenticationResult(); + this.refreshToken = result.refreshToken(); + return result.idToken(); + } + + private String fetchTokenWithSRP() { + try { + // Step 1: Generate SRP-A and initiate auth + SecureRandom random = new SecureRandom(); + BigInteger a = new BigInteger(EPHEMERAL_KEY_LENGTH, random); + BigInteger A = g.modPow(a, N); + Map authParameters = new HashMap<>(); - authParameters.put("REFRESH_TOKEN", this.refreshToken); + authParameters.put("USERNAME", username); + authParameters.put("SRP_A", A.toString(16)); - authRequest = InitiateAuthRequest.builder() + InitiateAuthRequest authRequest = InitiateAuthRequest.builder() .clientId(this.cognitoClientId) - // .userPoolId(this.cognitoUserPoolId) .authParameters(authParameters) .authFlow(AuthFlowType.USER_SRP_AUTH) .build(); - } else { - Map authParameters = new HashMap<>(); - authParameters.put("USERNAME", username); - authParameters.put("PASSWORD", password); - authRequest = InitiateAuthRequest.builder() + InitiateAuthResponse initAuthResponse = this.client.initiateAuth(authRequest); + + // Step 2: Process challenge + Map challengeParams = initAuthResponse.challengeParameters(); + String userIdForSRP = challengeParams.get("USER_ID_FOR_SRP"); + BigInteger B = new BigInteger(challengeParams.get("SRP_B"), 16); + String salt = challengeParams.get("SALT"); + String secretBlock = challengeParams.get("SECRET_BLOCK"); + + // Step 3: Calculate proof and respond to challenge + byte[] key = calculateSRPKey(a, B, salt, userIdForSRP); + String dateNow = String.valueOf(System.currentTimeMillis()); + + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + digest.update(cognitoUserPoolId.split("_")[1].getBytes(StandardCharsets.UTF_8)); + digest.update(userIdForSRP.getBytes(StandardCharsets.UTF_8)); + digest.update(Base64.getDecoder().decode(secretBlock)); + digest.update(dateNow.getBytes(StandardCharsets.UTF_8)); + + byte[] hmac = calculateHMAC(key, digest.digest()); + String proof = Base64.getEncoder().encodeToString(hmac); + + Map challengeResponses = new HashMap<>(); + challengeResponses.put("USERNAME", userIdForSRP); + challengeResponses.put("PASSWORD_CLAIM_SECRET_BLOCK", secretBlock); + challengeResponses.put("TIMESTAMP", dateNow); + challengeResponses.put("PASSWORD_CLAIM_SIGNATURE", proof); + + RespondToAuthChallengeRequest challengeRequest = RespondToAuthChallengeRequest.builder() .clientId(this.cognitoClientId) - // .userPoolId(this.cognitoUserPoolId) - .authParameters(authParameters) - .authFlow(AuthFlowType.USER_SRP_AUTH) + .challengeName("PASSWORD_VERIFIER") + .challengeResponses(challengeResponses) .build(); - } - InitiateAuthResponse response = this.client.initiateAuth(authRequest); - AuthenticationResultType result = response.authenticationResult(); + RespondToAuthChallengeResponse challengeResponse = this.client.respondToAuthChallenge(challengeRequest); + AuthenticationResultType result = challengeResponse.authenticationResult(); - this.refreshToken = result.refreshToken(); + this.refreshToken = result.refreshToken(); + return result.idToken(); - if (this.refreshToken == null) { - System.out.println("Failed to fetch token"); + } catch (Exception e) { + throw new RuntimeException("SRP authentication failed", e); } + } - return result.idToken(); + private byte[] calculateSRPKey(BigInteger a, BigInteger B, String salt, String userIdForSRP) + throws NoSuchAlgorithmException { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + + // Calculate u = H(A || B) + String concatenated = a.toString(16) + B.toString(16); + byte[] uHash = digest.digest(concatenated.getBytes(StandardCharsets.UTF_8)); + BigInteger u = new BigInteger(1, uHash); + + // Calculate x = H(salt || H(userIdForSRP || ":" || password)) + digest.reset(); + digest.update((userIdForSRP + ":" + password).getBytes(StandardCharsets.UTF_8)); + byte[] userHash = digest.digest(); + + digest.reset(); + digest.update(Base64.getDecoder().decode(salt)); + digest.update(userHash); + BigInteger x = new BigInteger(1, digest.digest()); + + // Calculate S = (B - k * g^x) ^ (a + u * x) % N + BigInteger gx = g.modPow(x, N); + BigInteger kgx = k.multiply(gx).mod(N); + BigInteger difference = B.subtract(kgx).mod(N); + BigInteger exponent = a.add(u.multiply(x)); + BigInteger S = difference.modPow(exponent, N); + + // Calculate K = H(S) + digest.reset(); + return digest.digest(S.toString(16).getBytes(StandardCharsets.UTF_8)); + } + + private byte[] calculateHMAC(byte[] key, byte[] message) { + try { + javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256"); + javax.crypto.spec.SecretKeySpec keySpec = new javax.crypto.spec.SecretKeySpec(key, "HmacSHA256"); + mac.init(keySpec); + return mac.doFinal(message); + } catch (Exception e) { + throw new RuntimeException("Failed to calculate HMAC", e); + } } } From 0682a59177876496bfb3da49bfe9e9f3643357ee Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Mon, 28 Apr 2025 15:33:35 +0900 Subject: [PATCH 21/36] add test --- .../city/makeour/FetchCognitoTokenTest.java | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 src/test/java/city/makeour/FetchCognitoTokenTest.java diff --git a/src/test/java/city/makeour/FetchCognitoTokenTest.java b/src/test/java/city/makeour/FetchCognitoTokenTest.java new file mode 100644 index 0000000..89eb9c1 --- /dev/null +++ b/src/test/java/city/makeour/FetchCognitoTokenTest.java @@ -0,0 +1,145 @@ +package city.makeour; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Field; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient; +import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthenticationResultType; +import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthRequest; +import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthResponse; +import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeRequest; +import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeResponse; + +class FetchCognitoTokenTest { + + private static final String TEST_USER_POOL_ID = "ap-northeast-1_testpool"; + private static final String TEST_CLIENT_ID = "test-client-id"; + private static final String TEST_USERNAME = "testuser"; + private static final String TEST_PASSWORD = "testpassword"; + private static final String TEST_ID_TOKEN = "test-id-token"; + private static final String TEST_REFRESH_TOKEN = "test-refresh-token"; + + @Mock + private CognitoIdentityProviderClient mockCognitoClient; + + private FetchCognitoToken tokenFetcher; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + + tokenFetcher = new FetchCognitoToken(TEST_USER_POOL_ID, TEST_CLIENT_ID); + tokenFetcher.setAuthParameters(TEST_USERNAME, TEST_PASSWORD); + + // モックしたクライアントを注入 + Field clientField = FetchCognitoToken.class.getDeclaredField("client"); + clientField.setAccessible(true); + clientField.set(tokenFetcher, mockCognitoClient); + } + + @Test + @DisplayName("コンストラクタで正しく初期化されることを確認") + void constructorShouldInitializeCorrectly() { + FetchCognitoToken fetcher = new FetchCognitoToken(TEST_USER_POOL_ID, TEST_CLIENT_ID); + + assertEquals(TEST_USER_POOL_ID, fetcher.cognitoUserPoolId); + assertEquals(TEST_CLIENT_ID, fetcher.cognitoClientId); + assertNotNull(fetcher.client); + } + + @Test + @DisplayName("認証パラメータが正しく設定されることを確認") + void setAuthParametersShouldSetCredentials() { + FetchCognitoToken fetcher = new FetchCognitoToken(TEST_USER_POOL_ID, TEST_CLIENT_ID); + fetcher.setAuthParameters(TEST_USERNAME, TEST_PASSWORD); + + assertEquals(TEST_USERNAME, fetcher.username); + assertEquals(TEST_PASSWORD, fetcher.password); + } + + @Test + @DisplayName("リフレッシュトークンを使用してトークンを取得できることを確認") + void fetchTokenWithRefreshTokenShouldSucceed() { + // リフレッシュトークンを設定 + tokenFetcher.refreshToken = TEST_REFRESH_TOKEN; + + // モックの応答を設定 + AuthenticationResultType authResult = AuthenticationResultType.builder() + .idToken(TEST_ID_TOKEN) + .refreshToken(TEST_REFRESH_TOKEN) + .build(); + + InitiateAuthResponse mockResponse = InitiateAuthResponse.builder() + .authenticationResult(authResult) + .build(); + + when(mockCognitoClient.initiateAuth(any(InitiateAuthRequest.class))) + .thenReturn(mockResponse); + + // トークン取得を実行 + String token = tokenFetcher.fetchToken(); + + assertEquals(TEST_ID_TOKEN, token); + assertEquals(TEST_REFRESH_TOKEN, tokenFetcher.refreshToken); + } + + @Test + @DisplayName("SRP認証でトークンを取得できることを確認") + void fetchTokenWithSRPShouldSucceed() { + // InitiateAuth のモック応答を設定 + InitiateAuthResponse initAuthResponse = InitiateAuthResponse.builder() + .challengeName("PASSWORD_VERIFIER") + .challengeParameters(new java.util.HashMap() { + { + put("USER_ID_FOR_SRP", TEST_USERNAME); + put("SRP_B", "123456789abcdef"); + put("SALT", "salt123"); + put("SECRET_BLOCK", "c2VjcmV0"); // Base64エンコードされた "secret" + } + }) + .build(); + + when(mockCognitoClient.initiateAuth(any(InitiateAuthRequest.class))) + .thenReturn(initAuthResponse); + + // RespondToAuthChallenge のモック応答を設定 + AuthenticationResultType authResult = AuthenticationResultType.builder() + .idToken(TEST_ID_TOKEN) + .refreshToken(TEST_REFRESH_TOKEN) + .build(); + + RespondToAuthChallengeResponse challengeResponse = RespondToAuthChallengeResponse.builder() + .authenticationResult(authResult) + .build(); + + when(mockCognitoClient.respondToAuthChallenge(any(RespondToAuthChallengeRequest.class))) + .thenReturn(challengeResponse); + + // トークン取得を実行 + String token = tokenFetcher.fetchToken(); + + assertEquals(TEST_ID_TOKEN, token); + assertEquals(TEST_REFRESH_TOKEN, tokenFetcher.refreshToken); + } + + @Test + @DisplayName("SRP認証でトークン取得のテスト") + void testFetchTokenWithSRP() { + FetchCognitoToken fetcher = new FetchCognitoToken( + "ap-northeast-1_nXSBLO7v6", + "3d7d0piq75halieshbi7o8keca"); + + fetcher.setAuthParameters("ushio.s@gmail.com", "ushioshugo"); + fetcher.fetchToken(); + } +} \ No newline at end of file From d976829b23614c75c101ec4d7ac28b3db8a67c99 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Mon, 28 Apr 2025 15:39:12 +0900 Subject: [PATCH 22/36] challenged --- pom.xml | 2 +- src/main/java/city/makeour/FetchCognitoToken.java | 11 +++++++++-- src/test/java/city/makeour/FetchCognitoTokenTest.java | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 882a5e8..e63abbd 100644 --- a/pom.xml +++ b/pom.xml @@ -53,7 +53,7 @@ software.amazon.awssdk - aws-sdk-java + cognitoidentityprovider 2.31.20 diff --git a/src/main/java/city/makeour/FetchCognitoToken.java b/src/main/java/city/makeour/FetchCognitoToken.java index 3398e0f..12a3d57 100644 --- a/src/main/java/city/makeour/FetchCognitoToken.java +++ b/src/main/java/city/makeour/FetchCognitoToken.java @@ -5,8 +5,12 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.Base64; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import software.amazon.awssdk.awscore.defaultsmode.DefaultsMode; @@ -103,7 +107,10 @@ private String fetchTokenWithSRP() { // Step 3: Calculate proof and respond to challenge byte[] key = calculateSRPKey(a, B, salt, userIdForSRP); - String dateNow = String.valueOf(System.currentTimeMillis()); + // Format timestamp as required by Cognito + ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE MMM d HH:mm:ss 'UTC' yyyy", Locale.US); + String dateNow = now.format(formatter); MessageDigest digest = MessageDigest.getInstance("SHA-256"); digest.update(cognitoUserPoolId.split("_")[1].getBytes(StandardCharsets.UTF_8)); @@ -133,7 +140,7 @@ private String fetchTokenWithSRP() { return result.idToken(); } catch (Exception e) { - throw new RuntimeException("SRP authentication failed", e); + throw new RuntimeException("SRP authentication failed" + e.getMessage(), e); } } diff --git a/src/test/java/city/makeour/FetchCognitoTokenTest.java b/src/test/java/city/makeour/FetchCognitoTokenTest.java index 89eb9c1..f3c0adc 100644 --- a/src/test/java/city/makeour/FetchCognitoTokenTest.java +++ b/src/test/java/city/makeour/FetchCognitoTokenTest.java @@ -139,7 +139,7 @@ void testFetchTokenWithSRP() { "ap-northeast-1_nXSBLO7v6", "3d7d0piq75halieshbi7o8keca"); - fetcher.setAuthParameters("ushio.s@gmail.com", "ushioshugo"); + fetcher.setAuthParameters("ushio.s@gmail.com", ""); fetcher.fetchToken(); } } \ No newline at end of file From c86619f59e78bf188975e15c701127dd6f52d6ed Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Tue, 29 Apr 2025 10:40:06 +0900 Subject: [PATCH 23/36] move file --- pom.xml | 9 +- .../makeour/moc/AuthenticationHelper.java | 175 ++++++++++++++++++ .../makeour/moc/CognitoAuthenticator.java | 86 +++++++++ .../makeour/{ => moc}/FetchCognitoToken.java | 2 +- .../makeour/moc/JavaCognitoLoginSample.java | 81 ++++++++ src/main/java/city/makeour/moc/MocClient.java | 2 - .../{ => moc}/TokenFetcherInterface.java | 2 +- .../moc/CognitoAuthenticationHelperTest.java | 39 ++++ .../{ => moc}/FetchCognitoTokenTest.java | 4 +- 9 files changed, 392 insertions(+), 8 deletions(-) create mode 100644 src/main/java/city/makeour/moc/AuthenticationHelper.java create mode 100644 src/main/java/city/makeour/moc/CognitoAuthenticator.java rename src/main/java/city/makeour/{ => moc}/FetchCognitoToken.java (99%) create mode 100644 src/main/java/city/makeour/moc/JavaCognitoLoginSample.java rename src/main/java/city/makeour/{ => moc}/TokenFetcherInterface.java (83%) create mode 100644 src/test/java/city/makeour/moc/CognitoAuthenticationHelperTest.java rename src/test/java/city/makeour/{ => moc}/FetchCognitoTokenTest.java (98%) diff --git a/pom.xml b/pom.xml index e63abbd..78d5058 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,7 @@ org.junit.jupiter junit-jupiter-api - 5.10.1 + 5.10.1 test @@ -59,7 +59,12 @@ com.github.makeOurCity ngsiv2-java - 0.0.2 + 0.0.2 + + + org.bouncycastle + bcprov-jdk15on + 1.70 diff --git a/src/main/java/city/makeour/moc/AuthenticationHelper.java b/src/main/java/city/makeour/moc/AuthenticationHelper.java new file mode 100644 index 0000000..1c4e235 --- /dev/null +++ b/src/main/java/city/makeour/moc/AuthenticationHelper.java @@ -0,0 +1,175 @@ +package city.makeour.moc; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.text.SimpleDateFormat; +import java.util.Base64; +import java.util.Date; +import java.util.Locale; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +public class AuthenticationHelper { + + private static final String HEX_N = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF"; + + private static final BigInteger N = new BigInteger(HEX_N, 16); + private static final BigInteger g = BigInteger.valueOf(2); + private static final BigInteger k; + + static { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + digest.update(padHex(N)); + digest.update(padHex(g)); + k = new BigInteger(1, digest.digest()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private final SecureRandom random; + private final BigInteger a; + private final BigInteger A; + private final String poolName; + + public AuthenticationHelper(String userPoolId) { + random = new SecureRandom(); + a = new BigInteger(1024, random).mod(N); + A = g.modPow(a, N); + poolName = userPoolId.split("_", 2)[1]; + } + + public BigInteger getA() { + return A; + } + + public byte[] getPasswordAuthenticationKey( + String username, String password, String srpBHex, String saltHex, String secretBlock) { + + BigInteger B = new BigInteger(srpBHex, 16); + if (B.mod(N).equals(BigInteger.ZERO)) { + throw new IllegalStateException("Invalid server B value"); + } + + BigInteger u = computeU(A, B); + BigInteger salt = new BigInteger(saltHex, 16); + + BigInteger x = calculateX(username, password, salt); + BigInteger S = (B.subtract(k.multiply(g.modPow(x, N)))).mod(N) + .modPow(a.add(u.multiply(x)), N) + .mod(N); + + return computeHKDF(padHex(S), u.toByteArray()); + } + + public String calculateSignature(String userIdForSRP, String secretBlock, String timestamp, byte[] key) { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(key, "HmacSHA256")); + + byte[] message = concatBytes( + poolName.getBytes(StandardCharsets.UTF_8), + userIdForSRP.getBytes(StandardCharsets.UTF_8), + Base64.getDecoder().decode(secretBlock), + timestamp.getBytes(StandardCharsets.UTF_8)); + + return Base64.getEncoder().encodeToString(mac.doFinal(message)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public String getCurrentFormattedTimestamp() { + SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM d HH:mm:ss 'UTC' yyyy", Locale.US); + sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); + return sdf.format(new Date()); + } + + // --- 内部ヘルパーメソッド --- + + private static byte[] padHex(BigInteger bigInt) { + byte[] hex = bigInt.toByteArray(); + if (hex.length == 256) { + return hex; + } else if (hex.length > 256) { + byte[] trimmed = new byte[256]; + System.arraycopy(hex, hex.length - 256, trimmed, 0, 256); + return trimmed; + } else { + byte[] padded = new byte[256]; + System.arraycopy(hex, 0, padded, 256 - hex.length, hex.length); + return padded; + } + } + + private static BigInteger computeU(BigInteger A, BigInteger B) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + digest.update(padHex(A)); + digest.update(padHex(B)); + return new BigInteger(1, digest.digest()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static BigInteger calculateX(String username, String password, BigInteger salt) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + String message = username + ":" + password; + byte[] userIdHash = digest.digest(message.getBytes(StandardCharsets.UTF_8)); + + digest.reset(); + digest.update(salt.toByteArray()); + digest.update(userIdHash); + + return new BigInteger(1, digest.digest()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static byte[] computeHKDF(byte[] ikm, byte[] salt) { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(salt, "HmacSHA256")); + byte[] prk = mac.doFinal(ikm); + + mac.init(new SecretKeySpec(prk, "HmacSHA256")); + byte[] info = "Caldera Derived Key".getBytes(StandardCharsets.UTF_8); + byte[] okm = mac.doFinal(info); + + byte[] result = new byte[16]; + System.arraycopy(okm, 0, result, 0, 16); + return result; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static byte[] concatBytes(byte[]... arrays) { + int totalLength = 0; + for (byte[] array : arrays) { + totalLength += array.length; + } + + byte[] result = new byte[totalLength]; + int pos = 0; + for (byte[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } +} diff --git a/src/main/java/city/makeour/moc/CognitoAuthenticator.java b/src/main/java/city/makeour/moc/CognitoAuthenticator.java new file mode 100644 index 0000000..fac8b7b --- /dev/null +++ b/src/main/java/city/makeour/moc/CognitoAuthenticator.java @@ -0,0 +1,86 @@ +package city.makeour.moc; + +import java.util.HashMap; +import java.util.Map; + +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient; +import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthFlowType; +import software.amazon.awssdk.services.cognitoidentityprovider.model.ChallengeNameType; +import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthRequest; +import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthResponse; +import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeRequest; +import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeResponse; + +public class CognitoAuthenticator { + + private final CognitoIdentityProviderClient cognitoClient; + private final String clientId; + private final String userPoolId; + + public CognitoAuthenticator(Region region, String clientId, String userPoolId) { + this.cognitoClient = CognitoIdentityProviderClient.builder() + .region(region) + .build(); + this.clientId = clientId; + this.userPoolId = userPoolId; + } + + public String login(String username, String password) { + AuthenticationHelper authHelper = new AuthenticationHelper(userPoolId); + + // ステップ1: SRP_A計算 + String srpAString = authHelper.getA().toString(16); + + // ステップ2: InitiateAuthで認証スタート + Map initAuthParams = new HashMap<>(); + initAuthParams.put("USERNAME", username); + initAuthParams.put("SRP_A", srpAString); + + InitiateAuthRequest initiateAuthRequest = InitiateAuthRequest.builder() + .authFlow(AuthFlowType.USER_SRP_AUTH) + .clientId(clientId) + .authParameters(initAuthParams) + .build(); + + InitiateAuthResponse initiateAuthResponse = cognitoClient.initiateAuth(initiateAuthRequest); + + if (!ChallengeNameType.PASSWORD_VERIFIER.toString().equals(initiateAuthResponse.challengeNameAsString())) { + throw new RuntimeException("Unexpected challenge: " + initiateAuthResponse.challengeNameAsString()); + } + + // ステップ3: チャレンジ受け取り + Map challengeParameters = initiateAuthResponse.challengeParameters(); + + String salt = challengeParameters.get("SALT"); + String srpB = challengeParameters.get("SRP_B"); + String secretBlock = challengeParameters.get("SECRET_BLOCK"); + String userIdForSRP = challengeParameters.get("USER_ID_FOR_SRP"); + + // ステップ4: パスワード認証キー計算 + byte[] passwordAuthKey = authHelper.getPasswordAuthenticationKey(username, password, srpB, salt, secretBlock); + + // ステップ5: 署名生成 + String timestamp = authHelper.getCurrentFormattedTimestamp(); + String signature = authHelper.calculateSignature(userIdForSRP, secretBlock, timestamp, passwordAuthKey); + + // ステップ6: RespondToAuthChallenge + Map challengeResponses = new HashMap<>(); + challengeResponses.put("PASSWORD_CLAIM_SECRET_BLOCK", secretBlock); + challengeResponses.put("PASSWORD_CLAIM_SIGNATURE", signature); + challengeResponses.put("TIMESTAMP", timestamp); + challengeResponses.put("USERNAME", username); + + RespondToAuthChallengeRequest challengeRequest = RespondToAuthChallengeRequest.builder() + .challengeName(ChallengeNameType.PASSWORD_VERIFIER) + .clientId(clientId) + .session(initiateAuthResponse.session()) + .challengeResponses(challengeResponses) + .build(); + + RespondToAuthChallengeResponse challengeResponse = cognitoClient.respondToAuthChallenge(challengeRequest); + + // ステップ7: 成功! IDToken取得 + return challengeResponse.authenticationResult().idToken(); + } +} diff --git a/src/main/java/city/makeour/FetchCognitoToken.java b/src/main/java/city/makeour/moc/FetchCognitoToken.java similarity index 99% rename from src/main/java/city/makeour/FetchCognitoToken.java rename to src/main/java/city/makeour/moc/FetchCognitoToken.java index 12a3d57..3cf31ac 100644 --- a/src/main/java/city/makeour/FetchCognitoToken.java +++ b/src/main/java/city/makeour/moc/FetchCognitoToken.java @@ -1,4 +1,4 @@ -package city.makeour; +package city.makeour.moc; import java.math.BigInteger; import java.nio.charset.StandardCharsets; diff --git a/src/main/java/city/makeour/moc/JavaCognitoLoginSample.java b/src/main/java/city/makeour/moc/JavaCognitoLoginSample.java new file mode 100644 index 0000000..c37bd95 --- /dev/null +++ b/src/main/java/city/makeour/moc/JavaCognitoLoginSample.java @@ -0,0 +1,81 @@ +package city.makeour.moc; + +import java.util.HashMap; +import java.util.Map; + +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient; +import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthFlowType; +import software.amazon.awssdk.services.cognitoidentityprovider.model.ChallengeNameType; +import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthRequest; +import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthResponse; +import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeRequest; +import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeResponse; + +public class JavaCognitoLoginSample { + + public static void main(String[] args) { + // あなたの設定 + String userPoolId = "ap-northeast-1_nXSBLO7v6"; + String clientId = "3d7d0piq75halieshbi7o8keca"; + String username = "ushio.s@gmail.com"; // ここはUUIDじゃない、メールアドレス! + String password = "ushioshugo"; + + // クライアント作成 + CognitoIdentityProviderClient client = CognitoIdentityProviderClient.builder() + .region(Region.AP_NORTHEAST_1) + .build(); + + // SRP認証用ヘルパー + AuthenticationHelper authHelper = new AuthenticationHelper(userPoolId); + + // SRP_A送信 + Map initAuthParams = new HashMap<>(); + initAuthParams.put("USERNAME", username); + initAuthParams.put("SRP_A", authHelper.getA().toString(16)); + + InitiateAuthRequest initiateAuthRequest = InitiateAuthRequest.builder() + .authFlow(AuthFlowType.USER_SRP_AUTH) + .clientId(clientId) + .authParameters(initAuthParams) + .build(); + + InitiateAuthResponse initiateAuthResponse = client.initiateAuth(initiateAuthRequest); + + if (!ChallengeNameType.PASSWORD_VERIFIER.toString().equals(initiateAuthResponse.challengeNameAsString())) { + throw new RuntimeException("Unexpected challenge: " + initiateAuthResponse.challengeNameAsString()); + } + + Map challengeParams = initiateAuthResponse.challengeParameters(); + String salt = challengeParams.get("SALT"); + String srpB = challengeParams.get("SRP_B"); + String secretBlock = challengeParams.get("SECRET_BLOCK"); + String userIdForSRP = challengeParams.get("USER_ID_FOR_SRP"); + + // SRP計算 + byte[] passwordKey = authHelper.getPasswordAuthenticationKey(username, password, srpB, salt, secretBlock); + String timestamp = authHelper.getCurrentFormattedTimestamp(); + String signature = authHelper.calculateSignature(userIdForSRP, secretBlock, timestamp, passwordKey); + + // チャレンジ応答 + Map challengeResponses = new HashMap<>(); + challengeResponses.put("PASSWORD_CLAIM_SECRET_BLOCK", secretBlock); + challengeResponses.put("PASSWORD_CLAIM_SIGNATURE", signature); + challengeResponses.put("TIMESTAMP", timestamp); + challengeResponses.put("USERNAME", username); + + RespondToAuthChallengeRequest challengeRequest = RespondToAuthChallengeRequest.builder() + .challengeName(ChallengeNameType.PASSWORD_VERIFIER) + .clientId(clientId) + .session(initiateAuthResponse.session()) + .challengeResponses(challengeResponses) + .build(); + + RespondToAuthChallengeResponse authChallengeResponse = client.respondToAuthChallenge(challengeRequest); + + // 成功したらIDトークン表示! + String idToken = authChallengeResponse.authenticationResult().idToken(); + System.out.println("ID Token:"); + System.out.println(idToken); + } +} diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index fe0a8f4..c3d92db 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -1,7 +1,5 @@ package city.makeour.moc; -import city.makeour.FetchCognitoToken; -import city.makeour.TokenFetcherInterface; import city.makeour.ngsi.v2.api.EntitiesApi; import city.makeour.ngsi.v2.invoker.ApiClient; diff --git a/src/main/java/city/makeour/TokenFetcherInterface.java b/src/main/java/city/makeour/moc/TokenFetcherInterface.java similarity index 83% rename from src/main/java/city/makeour/TokenFetcherInterface.java rename to src/main/java/city/makeour/moc/TokenFetcherInterface.java index 8925cdd..4d66c22 100644 --- a/src/main/java/city/makeour/TokenFetcherInterface.java +++ b/src/main/java/city/makeour/moc/TokenFetcherInterface.java @@ -1,4 +1,4 @@ -package city.makeour; +package city.makeour.moc; public interface TokenFetcherInterface { void setAuthParameters(String username, String password); diff --git a/src/test/java/city/makeour/moc/CognitoAuthenticationHelperTest.java b/src/test/java/city/makeour/moc/CognitoAuthenticationHelperTest.java new file mode 100644 index 0000000..b990b41 --- /dev/null +++ b/src/test/java/city/makeour/moc/CognitoAuthenticationHelperTest.java @@ -0,0 +1,39 @@ +package city.makeour.moc; + +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Description; + +import software.amazon.awssdk.regions.Region; + +public class CognitoAuthenticationHelperTest { + + // Test cases for the CognitoAuthenticationHelper class + // Add your test methods here + + static { + System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog"); + System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true"); + System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http", "DEBUG"); + System.setProperty("software.amazon.awssdk.eventstream.rpc", "debug"); + } + + // Example test method + @Test + @Description("CognitoAuthenticatorのloginメソッドのテスト") + public void testLogin() { + String userPoolId = "ap-northeast-1_nXSBLO7v6"; + String clientId = "3d7d0piq75halieshbi7o8keca"; + String username = "ushio.s@gmail.com"; + String password = "ushioshugo"; + + CognitoAuthenticator authenticator = new CognitoAuthenticator( + Region.AP_NORTHEAST_1, + clientId, + userPoolId); + + String idToken = authenticator.login(username, password); + + System.out.println("ID Token:"); + System.out.println(idToken); + } +} diff --git a/src/test/java/city/makeour/FetchCognitoTokenTest.java b/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java similarity index 98% rename from src/test/java/city/makeour/FetchCognitoTokenTest.java rename to src/test/java/city/makeour/moc/FetchCognitoTokenTest.java index f3c0adc..accd8be 100644 --- a/src/test/java/city/makeour/FetchCognitoTokenTest.java +++ b/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java @@ -1,4 +1,4 @@ -package city.makeour; +package city.makeour.moc; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -139,7 +139,7 @@ void testFetchTokenWithSRP() { "ap-northeast-1_nXSBLO7v6", "3d7d0piq75halieshbi7o8keca"); - fetcher.setAuthParameters("ushio.s@gmail.com", ""); + fetcher.setAuthParameters("ushio.s@gmail.com", "ushioshugo"); fetcher.fetchToken(); } } \ No newline at end of file From 118d819dc082e595c346de0b9c44b8b1e1ce1b9c Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Tue, 29 Apr 2025 11:00:14 +0900 Subject: [PATCH 24/36] remove password --- src/main/java/city/makeour/moc/JavaCognitoLoginSample.java | 2 +- src/test/java/city/makeour/moc/FetchCognitoTokenTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/city/makeour/moc/JavaCognitoLoginSample.java b/src/main/java/city/makeour/moc/JavaCognitoLoginSample.java index c37bd95..464e864 100644 --- a/src/main/java/city/makeour/moc/JavaCognitoLoginSample.java +++ b/src/main/java/city/makeour/moc/JavaCognitoLoginSample.java @@ -19,7 +19,7 @@ public static void main(String[] args) { String userPoolId = "ap-northeast-1_nXSBLO7v6"; String clientId = "3d7d0piq75halieshbi7o8keca"; String username = "ushio.s@gmail.com"; // ここはUUIDじゃない、メールアドレス! - String password = "ushioshugo"; + String password = ""; // クライアント作成 CognitoIdentityProviderClient client = CognitoIdentityProviderClient.builder() diff --git a/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java b/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java index accd8be..b2cc2a1 100644 --- a/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java +++ b/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java @@ -139,7 +139,7 @@ void testFetchTokenWithSRP() { "ap-northeast-1_nXSBLO7v6", "3d7d0piq75halieshbi7o8keca"); - fetcher.setAuthParameters("ushio.s@gmail.com", "ushioshugo"); + fetcher.setAuthParameters("ushio.s@gmail.com", ""); fetcher.fetchToken(); } } \ No newline at end of file From df6ff9e8f1b507bed4ddf9c870d280f084945801 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Tue, 29 Apr 2025 11:03:37 +0900 Subject: [PATCH 25/36] remove password --- .../java/city/makeour/moc/CognitoAuthenticationHelperTest.java | 2 +- src/test/java/city/makeour/moc/FetchCognitoTokenTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/city/makeour/moc/CognitoAuthenticationHelperTest.java b/src/test/java/city/makeour/moc/CognitoAuthenticationHelperTest.java index b990b41..1942321 100644 --- a/src/test/java/city/makeour/moc/CognitoAuthenticationHelperTest.java +++ b/src/test/java/city/makeour/moc/CognitoAuthenticationHelperTest.java @@ -24,7 +24,7 @@ public void testLogin() { String userPoolId = "ap-northeast-1_nXSBLO7v6"; String clientId = "3d7d0piq75halieshbi7o8keca"; String username = "ushio.s@gmail.com"; - String password = "ushioshugo"; + String password = ""; CognitoAuthenticator authenticator = new CognitoAuthenticator( Region.AP_NORTHEAST_1, diff --git a/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java b/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java index b2cc2a1..accd8be 100644 --- a/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java +++ b/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java @@ -139,7 +139,7 @@ void testFetchTokenWithSRP() { "ap-northeast-1_nXSBLO7v6", "3d7d0piq75halieshbi7o8keca"); - fetcher.setAuthParameters("ushio.s@gmail.com", ""); + fetcher.setAuthParameters("ushio.s@gmail.com", "ushioshugo"); fetcher.fetchToken(); } } \ No newline at end of file From ee104442fe5031d7006c5a02d9364ebef1f517f7 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Tue, 29 Apr 2025 11:03:51 +0900 Subject: [PATCH 26/36] remove password --- src/test/java/city/makeour/moc/FetchCognitoTokenTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java b/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java index accd8be..b2cc2a1 100644 --- a/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java +++ b/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java @@ -139,7 +139,7 @@ void testFetchTokenWithSRP() { "ap-northeast-1_nXSBLO7v6", "3d7d0piq75halieshbi7o8keca"); - fetcher.setAuthParameters("ushio.s@gmail.com", "ushioshugo"); + fetcher.setAuthParameters("ushio.s@gmail.com", ""); fetcher.fetchToken(); } } \ No newline at end of file From 2ff796bf946bfb5a952b9a17d29dcc2d9cf6f5f5 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Tue, 29 Apr 2025 16:09:39 +0900 Subject: [PATCH 27/36] Using useIdFroSRP --- src/main/java/city/makeour/moc/CognitoAuthenticator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/city/makeour/moc/CognitoAuthenticator.java b/src/main/java/city/makeour/moc/CognitoAuthenticator.java index fac8b7b..8445471 100644 --- a/src/main/java/city/makeour/moc/CognitoAuthenticator.java +++ b/src/main/java/city/makeour/moc/CognitoAuthenticator.java @@ -69,7 +69,7 @@ public String login(String username, String password) { challengeResponses.put("PASSWORD_CLAIM_SECRET_BLOCK", secretBlock); challengeResponses.put("PASSWORD_CLAIM_SIGNATURE", signature); challengeResponses.put("TIMESTAMP", timestamp); - challengeResponses.put("USERNAME", username); + challengeResponses.put("USERNAME", userIdForSRP); RespondToAuthChallengeRequest challengeRequest = RespondToAuthChallengeRequest.builder() .challengeName(ChallengeNameType.PASSWORD_VERIFIER) From a3871e33e58ced14da26d0b21c7b88154d2e3176 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Tue, 29 Apr 2025 17:41:35 +0900 Subject: [PATCH 28/36] add poolid to hash --- .../city/makeour/moc/AuthenticationHelper.java | 17 +++++++++++------ .../city/makeour/moc/CognitoAuthenticator.java | 3 ++- .../makeour/moc/JavaCognitoLoginSample.java | 3 ++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/main/java/city/makeour/moc/AuthenticationHelper.java b/src/main/java/city/makeour/moc/AuthenticationHelper.java index 1c4e235..e718bd5 100644 --- a/src/main/java/city/makeour/moc/AuthenticationHelper.java +++ b/src/main/java/city/makeour/moc/AuthenticationHelper.java @@ -55,7 +55,7 @@ public BigInteger getA() { } public byte[] getPasswordAuthenticationKey( - String username, String password, String srpBHex, String saltHex, String secretBlock) { + String poolName, String username, String password, String srpBHex, String saltHex, String secretBlock) { BigInteger B = new BigInteger(srpBHex, 16); if (B.mod(N).equals(BigInteger.ZERO)) { @@ -65,7 +65,7 @@ public byte[] getPasswordAuthenticationKey( BigInteger u = computeU(A, B); BigInteger salt = new BigInteger(saltHex, 16); - BigInteger x = calculateX(username, password, salt); + BigInteger x = calculateX(poolName, username, password, salt); BigInteger S = (B.subtract(k.multiply(g.modPow(x, N)))).mod(N) .modPow(a.add(u.multiply(x)), N) .mod(N); @@ -124,17 +124,22 @@ private static BigInteger computeU(BigInteger A, BigInteger B) { } } - private static BigInteger calculateX(String username, String password, BigInteger salt) { + private BigInteger calculateX(String poolName, String username, String password, BigInteger salt) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); - String message = username + ":" + password; - byte[] userIdHash = digest.digest(message.getBytes(StandardCharsets.UTF_8)); + // Step1: (poolName + username + ":" + password) をSHA-256 + String userIdHashInput = poolName + username + ":" + password; + byte[] userIdHash = digest.digest(userIdHashInput.getBytes(StandardCharsets.UTF_8)); + + // Step2: (saltバイト列 + userIdHash) をSHA-256 digest.reset(); digest.update(salt.toByteArray()); digest.update(userIdHash); - return new BigInteger(1, digest.digest()); + byte[] xHash = digest.digest(); + + return new BigInteger(1, xHash); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/src/main/java/city/makeour/moc/CognitoAuthenticator.java b/src/main/java/city/makeour/moc/CognitoAuthenticator.java index 8445471..18a3668 100644 --- a/src/main/java/city/makeour/moc/CognitoAuthenticator.java +++ b/src/main/java/city/makeour/moc/CognitoAuthenticator.java @@ -58,7 +58,8 @@ public String login(String username, String password) { String userIdForSRP = challengeParameters.get("USER_ID_FOR_SRP"); // ステップ4: パスワード認証キー計算 - byte[] passwordAuthKey = authHelper.getPasswordAuthenticationKey(username, password, srpB, salt, secretBlock); + byte[] passwordAuthKey = authHelper.getPasswordAuthenticationKey(userPoolId, username, password, srpB, salt, + secretBlock); // ステップ5: 署名生成 String timestamp = authHelper.getCurrentFormattedTimestamp(); diff --git a/src/main/java/city/makeour/moc/JavaCognitoLoginSample.java b/src/main/java/city/makeour/moc/JavaCognitoLoginSample.java index 464e864..4f90352 100644 --- a/src/main/java/city/makeour/moc/JavaCognitoLoginSample.java +++ b/src/main/java/city/makeour/moc/JavaCognitoLoginSample.java @@ -53,7 +53,8 @@ public static void main(String[] args) { String userIdForSRP = challengeParams.get("USER_ID_FOR_SRP"); // SRP計算 - byte[] passwordKey = authHelper.getPasswordAuthenticationKey(username, password, srpB, salt, secretBlock); + byte[] passwordKey = authHelper.getPasswordAuthenticationKey(userPoolId, username, password, srpB, salt, + secretBlock); String timestamp = authHelper.getCurrentFormattedTimestamp(); String signature = authHelper.calculateSignature(userIdForSRP, secretBlock, timestamp, passwordKey); From c3aae6c89c6c3dc5e54cfa59492958fd6cc0817b Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Tue, 29 Apr 2025 18:17:05 +0900 Subject: [PATCH 29/36] update hash funcs --- .../makeour/moc/AuthenticationHelper.java | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/main/java/city/makeour/moc/AuthenticationHelper.java b/src/main/java/city/makeour/moc/AuthenticationHelper.java index e718bd5..9d166f8 100644 --- a/src/main/java/city/makeour/moc/AuthenticationHelper.java +++ b/src/main/java/city/makeour/moc/AuthenticationHelper.java @@ -57,27 +57,41 @@ public BigInteger getA() { public byte[] getPasswordAuthenticationKey( String poolName, String username, String password, String srpBHex, String saltHex, String secretBlock) { - BigInteger B = new BigInteger(srpBHex, 16); + BigInteger B = new BigInteger(1, hexStringToByteArray(srpBHex)); if (B.mod(N).equals(BigInteger.ZERO)) { throw new IllegalStateException("Invalid server B value"); } BigInteger u = computeU(A, B); - BigInteger salt = new BigInteger(saltHex, 16); - + BigInteger salt = new BigInteger(1, hexStringToByteArray(saltHex)); BigInteger x = calculateX(poolName, username, password, salt); - BigInteger S = (B.subtract(k.multiply(g.modPow(x, N)))).mod(N) - .modPow(a.add(u.multiply(x)), N) - .mod(N); + + BigInteger base = B.subtract(k.multiply(g.modPow(x, N))).mod(N); + BigInteger exp = a.add(u.multiply(x)); + BigInteger S = base.modPow(exp, N).mod(N); return computeHKDF(padHex(S), u.toByteArray()); } + private static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i + 1), 16)); + } + return data; + } + public String calculateSignature(String userIdForSRP, String secretBlock, String timestamp, byte[] key) { try { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(key, "HmacSHA256")); + String cleanedSecretBlock = secretBlock.trim(); // 余計な改行やスペース除去 + byte[] secretBlockBytes = Base64.getDecoder().decode(cleanedSecretBlock); + System.out.println("secretBlockBytes.length = " + secretBlockBytes.length); + byte[] message = concatBytes( poolName.getBytes(StandardCharsets.UTF_8), userIdForSRP.getBytes(StandardCharsets.UTF_8), @@ -90,6 +104,14 @@ public String calculateSignature(String userIdForSRP, String secretBlock, String } } + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + public String getCurrentFormattedTimestamp() { SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM d HH:mm:ss 'UTC' yyyy", Locale.US); sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); From cff5f43c52652f7416f22ef67eb076d7f93b3182 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Wed, 30 Apr 2025 17:02:29 +0900 Subject: [PATCH 30/36] almost hash and crypt calc was correct --- .../makeour/moc/AuthenticationHelper.java | 147 ++++++-- src/main/java/city/makeour/moc/Exec.java | 356 ++++++++++++++++++ 2 files changed, 466 insertions(+), 37 deletions(-) create mode 100644 src/main/java/city/makeour/moc/Exec.java diff --git a/src/main/java/city/makeour/moc/AuthenticationHelper.java b/src/main/java/city/makeour/moc/AuthenticationHelper.java index 9d166f8..5848279 100644 --- a/src/main/java/city/makeour/moc/AuthenticationHelper.java +++ b/src/main/java/city/makeour/moc/AuthenticationHelper.java @@ -29,10 +29,10 @@ public class AuthenticationHelper { static { try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - digest.update(padHex(N)); - digest.update(padHex(g)); - k = new BigInteger(1, digest.digest()); + // MessageDigest digest = MessageDigest.getInstance("SHA-256"); + // digest.update(padHex(N)); + // digest.update(padHex(g)); + k = calculateK(N, g); } catch (Exception e) { throw new RuntimeException(e); } @@ -45,7 +45,7 @@ public class AuthenticationHelper { public AuthenticationHelper(String userPoolId) { random = new SecureRandom(); - a = new BigInteger(1024, random).mod(N); + a = new BigInteger(1, generateRandomBytes(128)).mod(N); A = g.modPow(a, N); poolName = userPoolId.split("_", 2)[1]; } @@ -54,6 +54,18 @@ public BigInteger getA() { return A; } + public String getHexA() { + + String hex = A.toString(16); + return String.format("%0256x", new BigInteger(hex, 16)); // 256桁になるように0埋め + } + + private static byte[] generateRandomBytes(int size) { + byte[] bytes = new byte[size]; + new java.security.SecureRandom().nextBytes(bytes); + return bytes; + } + public byte[] getPasswordAuthenticationKey( String poolName, String username, String password, String srpBHex, String saltHex, String secretBlock) { @@ -66,9 +78,9 @@ public byte[] getPasswordAuthenticationKey( BigInteger salt = new BigInteger(1, hexStringToByteArray(saltHex)); BigInteger x = calculateX(poolName, username, password, salt); - BigInteger base = B.subtract(k.multiply(g.modPow(x, N))).mod(N); - BigInteger exp = a.add(u.multiply(x)); - BigInteger S = base.modPow(exp, N).mod(N); + // BigInteger base = B.subtract(k.multiply(g.modPow(x, N))).mod(N); + // BigInteger exp = a.add(u.multiply(x)); + BigInteger S = calculateS(B, k, g, x, a, u, N); return computeHKDF(padHex(S), u.toByteArray()); } @@ -88,17 +100,26 @@ public String calculateSignature(String userIdForSRP, String secretBlock, String Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(key, "HmacSHA256")); - String cleanedSecretBlock = secretBlock.trim(); // 余計な改行やスペース除去 - byte[] secretBlockBytes = Base64.getDecoder().decode(cleanedSecretBlock); - System.out.println("secretBlockBytes.length = " + secretBlockBytes.length); + byte[] poolNameBytes = poolName.getBytes(StandardCharsets.UTF_8); + byte[] userIdBytes = userIdForSRP.getBytes(StandardCharsets.UTF_8); + byte[] secretBlockBytes = Base64.getDecoder().decode(secretBlock); + byte[] timestampBytes = timestamp.getBytes(StandardCharsets.UTF_8); + + byte[] message = new byte[poolNameBytes.length + userIdBytes.length + secretBlockBytes.length + + timestampBytes.length]; + + int pos = 0; + System.arraycopy(poolNameBytes, 0, message, pos, poolNameBytes.length); + pos += poolNameBytes.length; + System.arraycopy(userIdBytes, 0, message, pos, userIdBytes.length); + pos += userIdBytes.length; + System.arraycopy(secretBlockBytes, 0, message, pos, secretBlockBytes.length); + pos += secretBlockBytes.length; + System.arraycopy(timestampBytes, 0, message, pos, timestampBytes.length); - byte[] message = concatBytes( - poolName.getBytes(StandardCharsets.UTF_8), - userIdForSRP.getBytes(StandardCharsets.UTF_8), - Base64.getDecoder().decode(secretBlock), - timestamp.getBytes(StandardCharsets.UTF_8)); + byte[] rawSignature = mac.doFinal(message); - return Base64.getEncoder().encodeToString(mac.doFinal(message)); + return Base64.getEncoder().encodeToString(rawSignature); } catch (Exception e) { throw new RuntimeException(e); } @@ -112,6 +133,22 @@ private static String bytesToHex(byte[] bytes) { return sb.toString(); } + private static BigInteger calculateS(BigInteger B, BigInteger k, BigInteger g, BigInteger x, BigInteger a, + BigInteger u, BigInteger N) { + // Step1: base = (B - k * g^x) mod N + BigInteger gModPowX = g.modPow(x, N); + BigInteger kgx = k.multiply(gModPowX).mod(N); + BigInteger base = B.subtract(kgx).mod(N); + + // Step2: exponent = (a + u * x) + BigInteger exp = a.add(u.multiply(x)); + + // Step3: S = base ^ exp mod N + BigInteger S = base.modPow(exp, N); + + return S; + } + public String getCurrentFormattedTimestamp() { SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM d HH:mm:ss 'UTC' yyyy", Locale.US); sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); @@ -120,48 +157,79 @@ public String getCurrentFormattedTimestamp() { // --- 内部ヘルパーメソッド --- - private static byte[] padHex(BigInteger bigInt) { - byte[] hex = bigInt.toByteArray(); + private static byte[] padHex(BigInteger n) { + byte[] hex = n.toByteArray(); if (hex.length == 256) { return hex; - } else if (hex.length > 256) { - byte[] trimmed = new byte[256]; - System.arraycopy(hex, hex.length - 256, trimmed, 0, 256); - return trimmed; + } + byte[] padded = new byte[256]; + if (hex.length > 256) { + // Remove leading 0 byte (sign byte) + System.arraycopy(hex, hex.length - 256, padded, 0, 256); } else { - byte[] padded = new byte[256]; System.arraycopy(hex, 0, padded, 256 - hex.length, hex.length); - return padded; } + return padded; } private static BigInteger computeU(BigInteger A, BigInteger B) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); - digest.update(padHex(A)); - digest.update(padHex(B)); - return new BigInteger(1, digest.digest()); + + byte[] aPadded = padHex(A); + byte[] bPadded = padHex(B); + + byte[] combined = new byte[aPadded.length + bPadded.length]; + System.arraycopy(aPadded, 0, combined, 0, aPadded.length); + System.arraycopy(bPadded, 0, combined, aPadded.length, bPadded.length); + + byte[] uHash = digest.digest(combined); + + return new BigInteger(1, uHash); // 必ず符号なし } catch (Exception e) { throw new RuntimeException(e); } } - private BigInteger calculateX(String poolName, String username, String password, BigInteger salt) { + private static BigInteger calculateX(String poolName, String username, String password, BigInteger salt) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); - // Step1: (poolName + username + ":" + password) をSHA-256 - String userIdHashInput = poolName + username + ":" + password; - byte[] userIdHash = digest.digest(userIdHashInput.getBytes(StandardCharsets.UTF_8)); + // Step1: poolName + username + ":" + password を SHA-256 + String userPass = poolName + username + ":" + password; + byte[] userPassHash = digest.digest(userPass.getBytes(StandardCharsets.UTF_8)); - // Step2: (saltバイト列 + userIdHash) をSHA-256 + // Step2: saltバイト列をpadして + userPassHash を SHA-256 digest.reset(); - digest.update(salt.toByteArray()); - digest.update(userIdHash); + byte[] saltPadded = padHex(salt); - byte[] xHash = digest.digest(); + byte[] combined = new byte[saltPadded.length + userPassHash.length]; + System.arraycopy(saltPadded, 0, combined, 0, saltPadded.length); + System.arraycopy(userPassHash, 0, combined, saltPadded.length, userPassHash.length); - return new BigInteger(1, xHash); + byte[] xHash = digest.digest(combined); + + return new BigInteger(1, xHash); // 必ず符号なし + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static BigInteger calculateK(BigInteger N, BigInteger g) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + + byte[] nPadded = padHex(N); // さっき作ったやつ + byte[] gPadded = padHex(g); + + // 連結 + byte[] ng = new byte[nPadded.length + gPadded.length]; + System.arraycopy(nPadded, 0, ng, 0, nPadded.length); + System.arraycopy(gPadded, 0, ng, nPadded.length, gPadded.length); + + byte[] hash = digest.digest(ng); + + return new BigInteger(1, hash); // 必ず符号なし! } catch (Exception e) { throw new RuntimeException(e); } @@ -170,15 +238,20 @@ private BigInteger calculateX(String poolName, String username, String password, private static byte[] computeHKDF(byte[] ikm, byte[] salt) { try { Mac mac = Mac.getInstance("HmacSHA256"); + + // Step1: PRK = HMAC(salt, ikm) mac.init(new SecretKeySpec(salt, "HmacSHA256")); byte[] prk = mac.doFinal(ikm); + // Step2: OKM = HMAC(prk, info) mac.init(new SecretKeySpec(prk, "HmacSHA256")); byte[] info = "Caldera Derived Key".getBytes(StandardCharsets.UTF_8); byte[] okm = mac.doFinal(info); + // Step3: 最初の16バイトだけ取り出す byte[] result = new byte[16]; System.arraycopy(okm, 0, result, 0, 16); + return result; } catch (Exception e) { throw new RuntimeException(e); diff --git a/src/main/java/city/makeour/moc/Exec.java b/src/main/java/city/makeour/moc/Exec.java new file mode 100644 index 0000000..9814c93 --- /dev/null +++ b/src/main/java/city/makeour/moc/Exec.java @@ -0,0 +1,356 @@ +package city.makeour.moc; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Base64; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient; +import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthFlowType; +import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthenticationResultType; +import software.amazon.awssdk.services.cognitoidentityprovider.model.ChallengeNameType; +import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthRequest; +import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthResponse; +import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeRequest; +import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeResponse; + +public class Exec { + public static void main(String[] args) { + String userPoolId = "ap-northeast-1_nXSBLO7v6"; // ← ここあなたの設定 + String userPoolName = "nXSBLO7v6"; + String clientId = "3d7d0piq75halieshbi7o8keca"; // ← ここあなたの設定 + String username = "ushio.s@gmail.com"; // ← ここあなたの設定 + String password = ""; // ← ここあなたの設定 + Region region = Region.AP_NORTHEAST_1; + + AuthenticationHelper helper = new AuthenticationHelper(userPoolId); + + CognitoIdentityProviderClient cognitoClient = CognitoIdentityProviderClient.builder() + .region(region) + .credentialsProvider(DefaultCredentialsProvider.create()) + .build(); + + try { + InitiateAuthRequest initiateAuthRequest = InitiateAuthRequest.builder() + .authFlow(AuthFlowType.USER_SRP_AUTH) + .clientId(clientId) + .authParameters(Map.of( + "USERNAME", username, + "SRP_A", helper.getA().toString(16))) + .build(); + + System.out.println("SRP_A = " + helper.getHexA()); + + InitiateAuthResponse initiateAuthResponse = cognitoClient.initiateAuth(initiateAuthRequest); + Map challengeParameters = initiateAuthResponse.challengeParameters(); + + String userIdForSrp = challengeParameters.get("USER_ID_FOR_SRP"); + String salt = challengeParameters.get("SALT"); + String srpB = challengeParameters.get("SRP_B"); + String secretBlock = challengeParameters.get("SECRET_BLOCK"); + + // ← ここで一度ログ出力して比較用データを取得 + System.out.println("userIdForSrp = " + userIdForSrp); + System.out.println("salt = " + salt); + System.out.println("srpB = " + srpB); + System.out.println("secretBlock = " + secretBlock); + + byte[] signatureKey = helper.getPasswordAuthenticationKey(userPoolName, userIdForSrp, password, srpB, salt, + secretBlock); + String timestamp = helper.getCurrentFormattedTimestamp(); + String signature = helper.calculateSignature(userIdForSrp, secretBlock, timestamp, signatureKey); + + // ← signature計算結果も出力 + System.out.println("timestamp = " + timestamp); + System.out.println("signature = " + signature); + + Map challengeResponses = new HashMap<>(); + challengeResponses.put("USERNAME", userIdForSrp); + challengeResponses.put("PASSWORD_CLAIM_SECRET_BLOCK", secretBlock); + challengeResponses.put("PASSWORD_CLAIM_SIGNATURE", signature); + challengeResponses.put("TIMESTAMP", timestamp); + + RespondToAuthChallengeRequest respondRequest = RespondToAuthChallengeRequest.builder() + .challengeName(ChallengeNameType.PASSWORD_VERIFIER) + .clientId(clientId) + .challengeResponses(challengeResponses) + .build(); + + RespondToAuthChallengeResponse authChallengeResponse = cognitoClient.respondToAuthChallenge(respondRequest); + AuthenticationResultType authResult = authChallengeResponse.authenticationResult(); + + System.out.println("ID Token:"); + System.out.println(authResult.idToken()); + } catch (Exception e) { + e.printStackTrace(); + } finally { + cognitoClient.close(); + } + } + + public static class AuthenticationHelper { + private static final BigInteger N = new BigInteger(1, hexStringToByteArray( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD" + + + "3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F" + + + "24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552" + + + "BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF0" + + + "6F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64EC" + + + "FB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A" + + + "0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D1" + + + "20A93AD2CAFFFFFFFFFFFFFFFF")); + private static final BigInteger g = BigInteger.valueOf(2); + private static final BigInteger k = calculateK(N, g); + + private final BigInteger a; + private final BigInteger A; + private final String poolName; + + public AuthenticationHelper(String userPoolId) { + this.poolName = userPoolId; + // this.a = new BigInteger(1, generateRandomBytes(128)).mod(N); + this.a = new BigInteger("123456789abcdef123456789abcdef123456789abcdef", 16); + this.A = g.modPow(a, N); + } + + public BigInteger getA() { + return A; + } + + public String getHexA() { + String hex = A.toString(16); + return String.format("%0256x", new BigInteger(hex, 16)); // 256桁になるように0埋め + } + + private static byte[] generateRandomBytes(int size) { + byte[] bytes = new byte[size]; + new java.security.SecureRandom().nextBytes(bytes); + return bytes; + } + + public byte[] getPasswordAuthenticationKey(String poolName, String username, String password, String srpBHex, + String saltHex, + String secretBlock) { + // BigInteger B = new BigInteger(1, hexStringToByteArray(srpBHex)); + srpBHex = "7fbdbae7b2ba3f9eda5e48c8bb61f82fcc3d6ed1645947fa7512b718485ad98fd3f195192e1c6dcdaef988236f8c13944e37d11598342dad7671b4e0ca756ee4021a1ce7053aa5e9ab16955eafcf445ce3954fa3873df73c509f2c25e85d584f5b01d63a8f0cdaf6dfbca93965535d49650c4e956a6b34844e60f2a973012d83937b92493847e55de7b421adbca8ece7a5960b5987f7e5b4d1c4a4983255862e9c67748c16e219bfa519dfbb87e79d70d4dddf727b8e1cdcb2a4ed6a5673218e550f9ce30b1284bff95d760011794cd63c1bd6511113f0fd15003512ae134c3f996021328ff0dd847987b6f0387a8ae1c5cb458462757aacc97f2128a5548c44331ddaaf91f9bb8bff33c3a69bef3e4bbe9e851616958bf77e7ae3bae42d06dbbb158ad1a55a6df6d3b5a2ba863d6951c257394b28c22ed33f8d18bbfcc97f084952a620f8f0fabf8c107a01ae0800dff62c80764aee5733b8dca44eb5a14c2f3294cfef7883c35e31aa188319fe561a21a1b7d4a905864f9ce7dd58a100ccd3"; + BigInteger B = new BigInteger(1, hexStringToByteArray(srpBHex)); + if (B.mod(N).equals(BigInteger.ZERO)) { + throw new IllegalStateException("Invalid server B value"); + } + BigInteger u = computeU(A, B); + System.out.println("Java U = " + u.toString(16)); + BigInteger salt = new BigInteger(1, hexStringToByteArray(saltHex)); + BigInteger x = calculateX(poolName, username, password, salt); + System.out.println("Java x = " + x.toString(16)); + BigInteger S = calculateS(B, k, g, x, a, u, N); + System.out.println("Java S = " + S.toString(16)); + byte[] signatureKey = computeHKDF(padHex(S), u.toByteArray()); + System.out.println("Java K = " + Base64.getEncoder().encodeToString(signatureKey)); + + System.out.println("Java N = " + N.toString(16)); + return signatureKey; + } + + public String calculateSignature(String userIdForSRP, String secretBlock, String timestamp, byte[] key) { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(key, "HmacSHA256")); + + byte[] poolNameBytes = poolName.getBytes(StandardCharsets.UTF_8); + byte[] userIdBytes = userIdForSRP.getBytes(StandardCharsets.UTF_8); + byte[] secretBlockBytes = Base64.getDecoder().decode(secretBlock); + byte[] timestampBytes = timestamp.getBytes(StandardCharsets.UTF_8); + + byte[] message = new byte[poolNameBytes.length + userIdBytes.length + secretBlockBytes.length + + timestampBytes.length]; + int pos = 0; + System.arraycopy(poolNameBytes, 0, message, pos, poolNameBytes.length); + pos += poolNameBytes.length; + System.arraycopy(userIdBytes, 0, message, pos, userIdBytes.length); + pos += userIdBytes.length; + System.arraycopy(secretBlockBytes, 0, message, pos, secretBlockBytes.length); + pos += secretBlockBytes.length; + System.arraycopy(timestampBytes, 0, message, pos, timestampBytes.length); + + System.out.println("Java signature message = " + toHex(message)); + + byte[] rawSignature = mac.doFinal(message); + return Base64.getEncoder().encodeToString(rawSignature); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public String getCurrentFormattedTimestamp() { + SimpleDateFormat format = new SimpleDateFormat("EEE MMM d HH:mm:ss z yyyy", java.util.Locale.US); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + return format.format(new Date()); + } + + private static BigInteger calculateK(BigInteger N, BigInteger g) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] nPadded = padHex(N); + byte[] gPadded = padHex(g); + byte[] ng = new byte[nPadded.length + gPadded.length]; + System.arraycopy(nPadded, 0, ng, 0, nPadded.length); + System.arraycopy(gPadded, 0, ng, nPadded.length, gPadded.length); + byte[] hash = digest.digest(ng); + return new BigInteger(1, hash); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static BigInteger calculateX(String poolName, String username, String password, BigInteger salt) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + String userPass = poolName + username + ":" + password; + byte[] userPassHash = digest.digest(userPass.getBytes(StandardCharsets.UTF_8)); // inner hash + + // JS の padHex 相当:符号なしの even-length hex 文字列を作成 + String saltHex = salt.toString(16); + if (saltHex.length() % 2 != 0) + saltHex = "0" + saltHex; + if (saltHex.matches("^[89a-fA-F].*")) + saltHex = "00" + saltHex; + byte[] saltBytes = hexStringToByteArray(saltHex); + + byte[] combined = new byte[saltBytes.length + userPassHash.length]; + System.arraycopy(saltBytes, 0, combined, 0, saltBytes.length); + System.arraycopy(userPassHash, 0, combined, saltBytes.length, userPassHash.length); + + byte[] xHash = digest.digest(combined); // final hash + return new BigInteger(1, xHash); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static BigInteger computeU(BigInteger A, BigInteger B) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] aPadded = padHex(A); + byte[] bPadded = padHex(B); + System.out.println("padHex(A) = " + toHex(padHex(A))); + System.out.println("padHex(B) = " + toHex(padHex(B))); + byte[] combined = new byte[aPadded.length + bPadded.length]; + System.arraycopy(aPadded, 0, combined, 0, aPadded.length); + System.arraycopy(bPadded, 0, combined, aPadded.length, bPadded.length); + byte[] uHash = digest.digest(combined); + return new BigInteger(1, uHash); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static String toHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + + private static BigInteger calculateS(BigInteger B, BigInteger k, BigInteger g, BigInteger x, BigInteger a, + BigInteger u, BigInteger N) { + BigInteger gModPowX = g.modPow(x, N); + BigInteger kgx = k.multiply(gModPowX).mod(N); + BigInteger base = B.subtract(kgx).mod(N); + BigInteger exp = a.add(u.multiply(x)); + return base.modPow(exp, N); + } + + private static byte[] computeHKDF(byte[] ikm, byte[] salt) { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(salt, "HmacSHA256")); + byte[] prk = mac.doFinal(ikm); + + mac.init(new SecretKeySpec(prk, "HmacSHA256")); + byte[] info = "Caldera Derived Key".getBytes(StandardCharsets.UTF_8); + byte[] infoWithCounter = new byte[info.length + 1]; + System.arraycopy(info, 0, infoWithCounter, 0, info.length); + infoWithCounter[info.length] = 0x01; + + byte[] okm = mac.doFinal(infoWithCounter); + return Arrays.copyOfRange(okm, 0, 16); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static byte[] padHexOld(BigInteger n) { + byte[] hex = n.toByteArray(); + if (hex.length == 256) { + return hex; + } + byte[] padded = new byte[256]; + if (hex.length > 256) { + System.arraycopy(hex, hex.length - 256, padded, 0, 256); + } else { + System.arraycopy(hex, 0, padded, 256 - hex.length, hex.length); + } + return padded; + } + + private static byte[] padHex(BigInteger bigInt) { + boolean isNegative = bigInt.signum() < 0; + String hex = bigInt.abs().toString(16); + + // 奇数桁なら先頭に 0 を追加 + if (hex.length() % 2 != 0) { + hex = "0" + hex; + } + + // MSBが立っていれば "00" を先頭に追加(符号ビットを避ける) + if (hex.matches("^[89a-fA-F].*")) { + hex = "00" + hex; + } + + if (isNegative) { + // 補数計算: 反転 → +1 + StringBuilder flipped = new StringBuilder(); + for (char c : hex.toCharArray()) { + int nibble = ~Character.digit(c, 16) & 0xF; + flipped.append(Integer.toHexString(nibble)); + } + BigInteger flippedBigInt = new BigInteger(flipped.toString(), 16).add(BigInteger.ONE); + hex = flippedBigInt.toString(16); + + // MSBがFF8〜なら短縮される→不要(JSでも無視できるレベル) + if (hex.length() % 2 != 0) + hex = "0" + hex; + } + + return hexStringToByteArray(hex); + } + + private static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i + 1), 16)); + } + return data; + } + } +} From cb8000915d05ae67bcef23590698854b3406b1da Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Wed, 30 Apr 2025 23:01:54 +0900 Subject: [PATCH 31/36] worked! --- src/main/java/city/makeour/moc/Exec.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/java/city/makeour/moc/Exec.java b/src/main/java/city/makeour/moc/Exec.java index 9814c93..d844c91 100644 --- a/src/main/java/city/makeour/moc/Exec.java +++ b/src/main/java/city/makeour/moc/Exec.java @@ -81,6 +81,8 @@ public static void main(String[] args) { challengeResponses.put("PASSWORD_CLAIM_SIGNATURE", signature); challengeResponses.put("TIMESTAMP", timestamp); + System.out.println("challengeResponses = " + challengeResponses); + RespondToAuthChallengeRequest respondRequest = RespondToAuthChallengeRequest.builder() .challengeName(ChallengeNameType.PASSWORD_VERIFIER) .clientId(clientId) @@ -149,7 +151,8 @@ public byte[] getPasswordAuthenticationKey(String poolName, String username, Str String saltHex, String secretBlock) { // BigInteger B = new BigInteger(1, hexStringToByteArray(srpBHex)); - srpBHex = "7fbdbae7b2ba3f9eda5e48c8bb61f82fcc3d6ed1645947fa7512b718485ad98fd3f195192e1c6dcdaef988236f8c13944e37d11598342dad7671b4e0ca756ee4021a1ce7053aa5e9ab16955eafcf445ce3954fa3873df73c509f2c25e85d584f5b01d63a8f0cdaf6dfbca93965535d49650c4e956a6b34844e60f2a973012d83937b92493847e55de7b421adbca8ece7a5960b5987f7e5b4d1c4a4983255862e9c67748c16e219bfa519dfbb87e79d70d4dddf727b8e1cdcb2a4ed6a5673218e550f9ce30b1284bff95d760011794cd63c1bd6511113f0fd15003512ae134c3f996021328ff0dd847987b6f0387a8ae1c5cb458462757aacc97f2128a5548c44331ddaaf91f9bb8bff33c3a69bef3e4bbe9e851616958bf77e7ae3bae42d06dbbb158ad1a55a6df6d3b5a2ba863d6951c257394b28c22ed33f8d18bbfcc97f084952a620f8f0fabf8c107a01ae0800dff62c80764aee5733b8dca44eb5a14c2f3294cfef7883c35e31aa188319fe561a21a1b7d4a905864f9ce7dd58a100ccd3"; + // srpBHex = + // "6472ea13e9cd1f054fb17c211f26831c47a615ca8c42cdab2893c1a015965c658b93c8324e90436eb6c6cf730ffd7db2200f716b74e340f9b2c02654362f99f1f78c610f6c0d4bc4996963063e4ca6af87f3516536fda0b0116695ec7a564ec9992f1b6e862e86c4f326d205d3c1accad76e41eca1e5a83ffdf8a58b3c5673f21a26f5cbc8452bf84945b5898ddb5a4b120ff09de4a7a63cbd7ec11f9601b90735c65b02075054e448a64ba7ebf826682e1a1e92990714b8725862bb6a6f0b1eb803ad13fddcac087a8b79a2402fc15421261caaad1db51fc3133469ef5db789af25baddb513986d5e5555683fe5d47f2e0c0e9eb048e81ff3dc372a9b862345f0a4ab0818f72d89f464959f22795b38347627e3fee375ac8fbc51ab8d31e438dac6926c7b1c690d32c5c6a75ee37e84886973d18cb8dcb75c3e835007df6ddcc91d13e4e99b6e291d7e9260ddcfbee8620ddc2f9fc7406369c5d085cc3ceba7207b376df8806a88c3a3ff404eba276298edb936111b41d0f9042c07581dd0e1"; BigInteger B = new BigInteger(1, hexStringToByteArray(srpBHex)); if (B.mod(N).equals(BigInteger.ZERO)) { throw new IllegalStateException("Invalid server B value"); @@ -173,7 +176,7 @@ public String calculateSignature(String userIdForSRP, String secretBlock, String Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(key, "HmacSHA256")); - byte[] poolNameBytes = poolName.getBytes(StandardCharsets.UTF_8); + byte[] poolNameBytes = poolName.split("_")[1].getBytes(StandardCharsets.UTF_8); byte[] userIdBytes = userIdForSRP.getBytes(StandardCharsets.UTF_8); byte[] secretBlockBytes = Base64.getDecoder().decode(secretBlock); byte[] timestampBytes = timestamp.getBytes(StandardCharsets.UTF_8); @@ -189,7 +192,12 @@ public String calculateSignature(String userIdForSRP, String secretBlock, String pos += secretBlockBytes.length; System.arraycopy(timestampBytes, 0, message, pos, timestampBytes.length); - System.out.println("Java signature message = " + toHex(message)); + System.out.println("Java message (userPoolName) = " + new String(poolNameBytes)); + System.out.println("Java message (username) = " + new String(userIdBytes)); + System.out.println( + "Java message (secretBlock) = " + Base64.getEncoder().encodeToString(secretBlockBytes)); + System.out.println("Java message (timestamp) = " + new String(timestampBytes)); + System.out.println("Java message (hex) = " + toHex(message)); byte[] rawSignature = mac.doFinal(message); return Base64.getEncoder().encodeToString(rawSignature); @@ -199,7 +207,7 @@ public String calculateSignature(String userIdForSRP, String secretBlock, String } public String getCurrentFormattedTimestamp() { - SimpleDateFormat format = new SimpleDateFormat("EEE MMM d HH:mm:ss z yyyy", java.util.Locale.US); + SimpleDateFormat format = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", java.util.Locale.US); format.setTimeZone(TimeZone.getTimeZone("UTC")); return format.format(new Date()); } From efe5a37911853be5440116a406e3d492ea3a3676 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Fri, 2 May 2025 00:27:23 +0900 Subject: [PATCH 32/36] Add new classes --- .../city/makeour/moc/FetchCognitoToken.java | 210 +++++------------- src/main/java/city/makeour/moc/MocClient.java | 5 +- .../makeour/moc/TokenFetcherInterface.java | 5 +- .../java/city/makeour/moc/auth/srp/HKDF.java | 38 ++++ .../city/makeour/moc/auth/srp/Helper.java | 47 ++++ .../city/makeour/moc/auth/srp/LargeA.java | 22 ++ .../city/makeour/moc/auth/srp/LargeB.java | 16 ++ .../city/makeour/moc/auth/srp/LargeN.java | 26 +++ .../city/makeour/moc/auth/srp/LargeS.java | 41 ++++ .../city/makeour/moc/auth/srp/SmallA.java | 22 ++ .../city/makeour/moc/auth/srp/SmallG.java | 12 + .../city/makeour/moc/auth/srp/SmallK.java | 26 +++ .../city/makeour/moc/auth/srp/SmallU.java | 27 +++ .../city/makeour/moc/auth/srp/SmallX.java | 42 ++++ .../moc/auth/srp/SrpAuthenticationHelper.java | 88 ++++++++ 15 files changed, 474 insertions(+), 153 deletions(-) create mode 100644 src/main/java/city/makeour/moc/auth/srp/HKDF.java create mode 100644 src/main/java/city/makeour/moc/auth/srp/Helper.java create mode 100644 src/main/java/city/makeour/moc/auth/srp/LargeA.java create mode 100644 src/main/java/city/makeour/moc/auth/srp/LargeB.java create mode 100644 src/main/java/city/makeour/moc/auth/srp/LargeN.java create mode 100644 src/main/java/city/makeour/moc/auth/srp/LargeS.java create mode 100644 src/main/java/city/makeour/moc/auth/srp/SmallA.java create mode 100644 src/main/java/city/makeour/moc/auth/srp/SmallG.java create mode 100644 src/main/java/city/makeour/moc/auth/srp/SmallK.java create mode 100644 src/main/java/city/makeour/moc/auth/srp/SmallU.java create mode 100644 src/main/java/city/makeour/moc/auth/srp/SmallX.java create mode 100644 src/main/java/city/makeour/moc/auth/srp/SrpAuthenticationHelper.java diff --git a/src/main/java/city/makeour/moc/FetchCognitoToken.java b/src/main/java/city/makeour/moc/FetchCognitoToken.java index 3cf31ac..0cef83f 100644 --- a/src/main/java/city/makeour/moc/FetchCognitoToken.java +++ b/src/main/java/city/makeour/moc/FetchCognitoToken.java @@ -1,23 +1,17 @@ package city.makeour.moc; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; +import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Base64; import java.util.HashMap; -import java.util.Locale; import java.util.Map; -import software.amazon.awssdk.awscore.defaultsmode.DefaultsMode; +import city.makeour.moc.auth.srp.SrpAuthenticationHelper; +import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient; import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthFlowType; import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthenticationResultType; +import software.amazon.awssdk.services.cognitoidentityprovider.model.ChallengeNameType; import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthRequest; import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthResponse; import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeRequest; @@ -25,21 +19,29 @@ public class FetchCognitoToken implements TokenFetcherInterface { - protected CognitoIdentityProviderClient client; - protected String cognitoUserPoolId; - protected String cognitoClientId; - protected String refreshToken; + private String cognitoUserPoolId; + private String cognitoClientId; + private String username; + private String password; + private Region region = Region.AP_NORTHEAST_1; - protected String username; - protected String password; + private SrpAuthenticationHelper helper; + private CognitoIdentityProviderClient cognitoClient; public FetchCognitoToken(String cognitoUserPoolId, String cognitoClientId) { - this.cognitoClientId = cognitoClientId; + this(Region.AP_NORTHEAST_1, cognitoUserPoolId, cognitoClientId); + } + + public FetchCognitoToken(Region region, String cognitoUserPoolId, String cognitoClientId) { this.cognitoUserPoolId = cognitoUserPoolId; - this.refreshToken = null; - this.client = CognitoIdentityProviderClient.builder() - .defaultsMode(DefaultsMode.STANDARD) - .region(Region.AP_NORTHEAST_1) + this.cognitoClientId = cognitoClientId; + this.region = region; + + this.helper = new SrpAuthenticationHelper(this.cognitoUserPoolId); + + this.cognitoClient = CognitoIdentityProviderClient.builder() + .region(this.region) + .credentialsProvider(DefaultCredentialsProvider.create()) .build(); } @@ -48,141 +50,47 @@ public void setAuthParameters(String username, String password) { this.password = password; } - // SRPに必要な定数 - private static final BigInteger N = new BigInteger( - "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", - 16); - private static final BigInteger g = BigInteger.valueOf(2); - private static final BigInteger k = BigInteger.valueOf(3); - private static final int EPHEMERAL_KEY_LENGTH = 1024; - - public String fetchToken() { - if (this.refreshToken != null) { - return fetchTokenWithRefresh(); - } - return fetchTokenWithSRP(); - } - - private String fetchTokenWithRefresh() { - Map authParameters = new HashMap<>(); - authParameters.put("REFRESH_TOKEN", this.refreshToken); - - InitiateAuthRequest authRequest = InitiateAuthRequest.builder() + public String fetchTokenWithSrpAuth() throws InvalidKeyException, NoSuchAlgorithmException { + InitiateAuthRequest initiateAuthRequest = InitiateAuthRequest.builder() + .authFlow(AuthFlowType.USER_SRP_AUTH) .clientId(this.cognitoClientId) - .authParameters(authParameters) - .authFlow(AuthFlowType.REFRESH_TOKEN_AUTH) + .authParameters(Map.of( + "USERNAME", this.username, + "SRP_A", helper.getA())) + .build(); + InitiateAuthResponse initiateAuthResponse = cognitoClient.initiateAuth(initiateAuthRequest); + Map challengeParameters = initiateAuthResponse.challengeParameters(); + + String userIdForSrp = challengeParameters.get("USER_ID_FOR_SRP"); + String salt = challengeParameters.get("SALT"); + String srpB = challengeParameters.get("SRP_B"); + String secretBlock = challengeParameters.get("SECRET_BLOCK"); + + byte[] signatureKey = helper.getPasswordAuthenticationKey(userIdForSrp, password, + srpB, salt, + secretBlock); + String timestamp = helper.getCurrentFormattedTimestamp(); + String signature = helper.calculateSignature(userIdForSrp, secretBlock, timestamp, signatureKey); + + Map challengeResponses = new HashMap<>(); + challengeResponses.put("USERNAME", userIdForSrp); + challengeResponses.put("PASSWORD_CLAIM_SECRET_BLOCK", secretBlock); + challengeResponses.put("PASSWORD_CLAIM_SIGNATURE", signature); + challengeResponses.put("TIMESTAMP", timestamp); + + RespondToAuthChallengeRequest respondRequest = RespondToAuthChallengeRequest.builder() + .challengeName(ChallengeNameType.PASSWORD_VERIFIER) + .clientId(this.cognitoClientId) + .challengeResponses(challengeResponses) .build(); - InitiateAuthResponse response = this.client.initiateAuth(authRequest); - AuthenticationResultType result = response.authenticationResult(); - this.refreshToken = result.refreshToken(); - return result.idToken(); - } - - private String fetchTokenWithSRP() { - try { - // Step 1: Generate SRP-A and initiate auth - SecureRandom random = new SecureRandom(); - BigInteger a = new BigInteger(EPHEMERAL_KEY_LENGTH, random); - BigInteger A = g.modPow(a, N); - - Map authParameters = new HashMap<>(); - authParameters.put("USERNAME", username); - authParameters.put("SRP_A", A.toString(16)); - - InitiateAuthRequest authRequest = InitiateAuthRequest.builder() - .clientId(this.cognitoClientId) - .authParameters(authParameters) - .authFlow(AuthFlowType.USER_SRP_AUTH) - .build(); - - InitiateAuthResponse initAuthResponse = this.client.initiateAuth(authRequest); - - // Step 2: Process challenge - Map challengeParams = initAuthResponse.challengeParameters(); - String userIdForSRP = challengeParams.get("USER_ID_FOR_SRP"); - BigInteger B = new BigInteger(challengeParams.get("SRP_B"), 16); - String salt = challengeParams.get("SALT"); - String secretBlock = challengeParams.get("SECRET_BLOCK"); - - // Step 3: Calculate proof and respond to challenge - byte[] key = calculateSRPKey(a, B, salt, userIdForSRP); - // Format timestamp as required by Cognito - ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE MMM d HH:mm:ss 'UTC' yyyy", Locale.US); - String dateNow = now.format(formatter); - - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - digest.update(cognitoUserPoolId.split("_")[1].getBytes(StandardCharsets.UTF_8)); - digest.update(userIdForSRP.getBytes(StandardCharsets.UTF_8)); - digest.update(Base64.getDecoder().decode(secretBlock)); - digest.update(dateNow.getBytes(StandardCharsets.UTF_8)); - - byte[] hmac = calculateHMAC(key, digest.digest()); - String proof = Base64.getEncoder().encodeToString(hmac); - - Map challengeResponses = new HashMap<>(); - challengeResponses.put("USERNAME", userIdForSRP); - challengeResponses.put("PASSWORD_CLAIM_SECRET_BLOCK", secretBlock); - challengeResponses.put("TIMESTAMP", dateNow); - challengeResponses.put("PASSWORD_CLAIM_SIGNATURE", proof); - - RespondToAuthChallengeRequest challengeRequest = RespondToAuthChallengeRequest.builder() - .clientId(this.cognitoClientId) - .challengeName("PASSWORD_VERIFIER") - .challengeResponses(challengeResponses) - .build(); - - RespondToAuthChallengeResponse challengeResponse = this.client.respondToAuthChallenge(challengeRequest); - AuthenticationResultType result = challengeResponse.authenticationResult(); - - this.refreshToken = result.refreshToken(); - return result.idToken(); - - } catch (Exception e) { - throw new RuntimeException("SRP authentication failed" + e.getMessage(), e); - } - } + RespondToAuthChallengeResponse authChallengeResponse = cognitoClient.respondToAuthChallenge(respondRequest); + AuthenticationResultType authResult = authChallengeResponse.authenticationResult(); - private byte[] calculateSRPKey(BigInteger a, BigInteger B, String salt, String userIdForSRP) - throws NoSuchAlgorithmException { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - - // Calculate u = H(A || B) - String concatenated = a.toString(16) + B.toString(16); - byte[] uHash = digest.digest(concatenated.getBytes(StandardCharsets.UTF_8)); - BigInteger u = new BigInteger(1, uHash); - - // Calculate x = H(salt || H(userIdForSRP || ":" || password)) - digest.reset(); - digest.update((userIdForSRP + ":" + password).getBytes(StandardCharsets.UTF_8)); - byte[] userHash = digest.digest(); - - digest.reset(); - digest.update(Base64.getDecoder().decode(salt)); - digest.update(userHash); - BigInteger x = new BigInteger(1, digest.digest()); - - // Calculate S = (B - k * g^x) ^ (a + u * x) % N - BigInteger gx = g.modPow(x, N); - BigInteger kgx = k.multiply(gx).mod(N); - BigInteger difference = B.subtract(kgx).mod(N); - BigInteger exponent = a.add(u.multiply(x)); - BigInteger S = difference.modPow(exponent, N); - - // Calculate K = H(S) - digest.reset(); - return digest.digest(S.toString(16).getBytes(StandardCharsets.UTF_8)); + return authResult.idToken(); } - private byte[] calculateHMAC(byte[] key, byte[] message) { - try { - javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256"); - javax.crypto.spec.SecretKeySpec keySpec = new javax.crypto.spec.SecretKeySpec(key, "HmacSHA256"); - mac.init(keySpec); - return mac.doFinal(message); - } catch (Exception e) { - throw new RuntimeException("Failed to calculate HMAC", e); - } + public String fetchToken() throws InvalidKeyException, NoSuchAlgorithmException { + return this.fetchTokenWithSrpAuth(); } } diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index c3d92db..fb76b46 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -1,5 +1,8 @@ package city.makeour.moc; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + import city.makeour.ngsi.v2.api.EntitiesApi; import city.makeour.ngsi.v2.invoker.ApiClient; @@ -29,7 +32,7 @@ public void setMocAuthInfo(String cognitoUserPoolId, String cognitoClientId) { this.tokenFetcher = new FetchCognitoToken(cognitoUserPoolId, cognitoClientId); } - public void login(String username, String password) { + public void login(String username, String password) throws InvalidKeyException, NoSuchAlgorithmException { if (this.tokenFetcher == null) { throw new IllegalStateException("MocClient is not initialized with Cognito auth info."); } diff --git a/src/main/java/city/makeour/moc/TokenFetcherInterface.java b/src/main/java/city/makeour/moc/TokenFetcherInterface.java index 4d66c22..48b13f2 100644 --- a/src/main/java/city/makeour/moc/TokenFetcherInterface.java +++ b/src/main/java/city/makeour/moc/TokenFetcherInterface.java @@ -1,7 +1,10 @@ package city.makeour.moc; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + public interface TokenFetcherInterface { void setAuthParameters(String username, String password); - String fetchToken(); + String fetchToken() throws InvalidKeyException, NoSuchAlgorithmException; } diff --git a/src/main/java/city/makeour/moc/auth/srp/HKDF.java b/src/main/java/city/makeour/moc/auth/srp/HKDF.java new file mode 100644 index 0000000..05c4815 --- /dev/null +++ b/src/main/java/city/makeour/moc/auth/srp/HKDF.java @@ -0,0 +1,38 @@ +package city.makeour.moc.auth.srp; + +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +public class HKDF { + + private LargeS S; + private SmallU u; + + public HKDF(LargeS S, SmallU u) { + this.S = S; + this.u = u; + } + + public byte[] value() throws NoSuchAlgorithmException, InvalidKeyException { + byte[] ikm = Helper.padHex(S.value()); + byte[] salt = u.value().toByteArray(); + + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(salt, "HmacSHA256")); + byte[] prk = mac.doFinal(ikm); + + mac.init(new SecretKeySpec(prk, "HmacSHA256")); + byte[] info = "Caldera Derived Key".getBytes(StandardCharsets.UTF_8); + byte[] infoWithCounter = new byte[info.length + 1]; + System.arraycopy(info, 0, infoWithCounter, 0, info.length); + infoWithCounter[info.length] = 0x01; + + byte[] okm = mac.doFinal(infoWithCounter); + return Arrays.copyOfRange(okm, 0, 16); + } +} diff --git a/src/main/java/city/makeour/moc/auth/srp/Helper.java b/src/main/java/city/makeour/moc/auth/srp/Helper.java new file mode 100644 index 0000000..1251617 --- /dev/null +++ b/src/main/java/city/makeour/moc/auth/srp/Helper.java @@ -0,0 +1,47 @@ +package city.makeour.moc.auth.srp; + +import java.math.BigInteger; + +public class Helper { + public static byte[] padHex(BigInteger bigInt) { + boolean isNegative = bigInt.signum() < 0; + String hex = bigInt.abs().toString(16); + + // 奇数桁なら先頭に 0 を追加 + if (hex.length() % 2 != 0) { + hex = "0" + hex; + } + + // MSBが立っていれば "00" を先頭に追加(符号ビットを避ける) + if (hex.matches("^[89a-fA-F].*")) { + hex = "00" + hex; + } + + if (isNegative) { + // 補数計算: 反転 → +1 + StringBuilder flipped = new StringBuilder(); + for (char c : hex.toCharArray()) { + int nibble = ~Character.digit(c, 16) & 0xF; + flipped.append(Integer.toHexString(nibble)); + } + BigInteger flippedBigInt = new BigInteger(flipped.toString(), 16).add(BigInteger.ONE); + hex = flippedBigInt.toString(16); + + // MSBがFF8〜なら短縮される→不要(JSでも無視できるレベル) + if (hex.length() % 2 != 0) + hex = "0" + hex; + } + + return hexStringToByteArray(hex); + } + + public static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i + 1), 16)); + } + return data; + } +} diff --git a/src/main/java/city/makeour/moc/auth/srp/LargeA.java b/src/main/java/city/makeour/moc/auth/srp/LargeA.java new file mode 100644 index 0000000..3bc52ff --- /dev/null +++ b/src/main/java/city/makeour/moc/auth/srp/LargeA.java @@ -0,0 +1,22 @@ +package city.makeour.moc.auth.srp; + +import java.math.BigInteger; + +public class LargeA { + private LargeN N; + private SmallA a; + private SmallG g; + + public LargeA(LargeN N, SmallA a, SmallG g) { + this.N = N; + this.a = a; + this.g = g; + } + + public BigInteger value() { + BigInteger gValue = this.g.value(); + BigInteger aValue = this.a.value(); + BigInteger NValue = this.N.value(); + return gValue.modPow(aValue, NValue); + } +} diff --git a/src/main/java/city/makeour/moc/auth/srp/LargeB.java b/src/main/java/city/makeour/moc/auth/srp/LargeB.java new file mode 100644 index 0000000..dcbe9bc --- /dev/null +++ b/src/main/java/city/makeour/moc/auth/srp/LargeB.java @@ -0,0 +1,16 @@ +package city.makeour.moc.auth.srp; + +import java.math.BigInteger; + +public class LargeB { + + private String B; + + public LargeB(String B) { + this.B = B; + } + + public BigInteger value() { + return new BigInteger(1, Helper.hexStringToByteArray(this.B)); + } +} diff --git a/src/main/java/city/makeour/moc/auth/srp/LargeN.java b/src/main/java/city/makeour/moc/auth/srp/LargeN.java new file mode 100644 index 0000000..7b9538c --- /dev/null +++ b/src/main/java/city/makeour/moc/auth/srp/LargeN.java @@ -0,0 +1,26 @@ +package city.makeour.moc.auth.srp; + +import java.math.BigInteger; + +public class LargeN { + private static final BigInteger N = new BigInteger(1, Helper.hexStringToByteArray( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD" + + + "3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F" + + + "24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552" + + + "BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF0" + + + "6F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64EC" + + + "FB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A" + + + "0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D1" + + + "20A93AD2CAFFFFFFFFFFFFFFFF")); + + public BigInteger value() { + return N; + } +} diff --git a/src/main/java/city/makeour/moc/auth/srp/LargeS.java b/src/main/java/city/makeour/moc/auth/srp/LargeS.java new file mode 100644 index 0000000..010a73e --- /dev/null +++ b/src/main/java/city/makeour/moc/auth/srp/LargeS.java @@ -0,0 +1,41 @@ +package city.makeour.moc.auth.srp; + +import java.math.BigInteger; +import java.security.NoSuchAlgorithmException; + +public class LargeS { + + private LargeB B; + private SmallK k; + private SmallG g; + private SmallX x; + private SmallA a; + private SmallU u; + private LargeN N; + + public LargeS(LargeB B, SmallK k, SmallG g, SmallX x, SmallA a, SmallU u, LargeN N) { + this.B = B; + this.k = k; + this.g = g; + this.x = x; + this.a = a; + this.u = u; + this.N = N; + } + + public BigInteger value() throws NoSuchAlgorithmException { + BigInteger NValue = this.N.value(); + BigInteger BValue = this.B.value(); + BigInteger xValue = this.x.value(); + BigInteger aValue = this.a.value(); + BigInteger uValue = this.u.value(); + BigInteger kValue = this.k.value(); + BigInteger gValue = this.g.value(); + + BigInteger gModPowX = gValue.modPow(xValue, NValue); + BigInteger kgx = kValue.multiply(gModPowX).mod(NValue); + BigInteger base = BValue.subtract(kgx).mod(NValue); + BigInteger exp = aValue.add(uValue.multiply(xValue)); + return base.modPow(exp, NValue); + } +} diff --git a/src/main/java/city/makeour/moc/auth/srp/SmallA.java b/src/main/java/city/makeour/moc/auth/srp/SmallA.java new file mode 100644 index 0000000..ae4ba17 --- /dev/null +++ b/src/main/java/city/makeour/moc/auth/srp/SmallA.java @@ -0,0 +1,22 @@ +package city.makeour.moc.auth.srp; + +import java.math.BigInteger; + +public class SmallA { + + private LargeN N; + + public SmallA(LargeN N) { + this.N = N; + } + + public BigInteger value() { + return new BigInteger(1, generateRandomBytes(128)).mod(this.N.value()); + } + + private static byte[] generateRandomBytes(int size) { + byte[] bytes = new byte[size]; + new java.security.SecureRandom().nextBytes(bytes); + return bytes; + } +} diff --git a/src/main/java/city/makeour/moc/auth/srp/SmallG.java b/src/main/java/city/makeour/moc/auth/srp/SmallG.java new file mode 100644 index 0000000..339463a --- /dev/null +++ b/src/main/java/city/makeour/moc/auth/srp/SmallG.java @@ -0,0 +1,12 @@ +package city.makeour.moc.auth.srp; + +import java.math.BigInteger; + +public class SmallG { + private static final BigInteger G = BigInteger.valueOf(2);; + + public BigInteger value() { + return G; + } + +} diff --git a/src/main/java/city/makeour/moc/auth/srp/SmallK.java b/src/main/java/city/makeour/moc/auth/srp/SmallK.java new file mode 100644 index 0000000..7debdb7 --- /dev/null +++ b/src/main/java/city/makeour/moc/auth/srp/SmallK.java @@ -0,0 +1,26 @@ +package city.makeour.moc.auth.srp; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class SmallK { + private LargeN N; + private SmallG g; + + SmallK(LargeN N, SmallG g) { + this.N = N; + this.g = g; + } + + public BigInteger value() throws NoSuchAlgorithmException { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] nPadded = Helper.padHex(this.N.value()); + byte[] gPadded = Helper.padHex(this.g.value()); + byte[] ng = new byte[nPadded.length + gPadded.length]; + System.arraycopy(nPadded, 0, ng, 0, nPadded.length); + System.arraycopy(gPadded, 0, ng, nPadded.length, gPadded.length); + byte[] hash = digest.digest(ng); + return new BigInteger(1, hash); + } +} diff --git a/src/main/java/city/makeour/moc/auth/srp/SmallU.java b/src/main/java/city/makeour/moc/auth/srp/SmallU.java new file mode 100644 index 0000000..c246211 --- /dev/null +++ b/src/main/java/city/makeour/moc/auth/srp/SmallU.java @@ -0,0 +1,27 @@ +package city.makeour.moc.auth.srp; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class SmallU { + + private LargeA A; + private LargeB B; + + public SmallU(LargeA A, LargeB B) { + this.A = A; + this.B = B; + } + + public BigInteger value() throws NoSuchAlgorithmException { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] aPadded = Helper.padHex(A.value()); + byte[] bPadded = Helper.padHex(B.value()); + byte[] combined = new byte[aPadded.length + bPadded.length]; + System.arraycopy(aPadded, 0, combined, 0, aPadded.length); + System.arraycopy(bPadded, 0, combined, aPadded.length, bPadded.length); + byte[] uHash = digest.digest(combined); + return new BigInteger(1, uHash); + } +} diff --git a/src/main/java/city/makeour/moc/auth/srp/SmallX.java b/src/main/java/city/makeour/moc/auth/srp/SmallX.java new file mode 100644 index 0000000..6e79cec --- /dev/null +++ b/src/main/java/city/makeour/moc/auth/srp/SmallX.java @@ -0,0 +1,42 @@ +package city.makeour.moc.auth.srp; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class SmallX { + + private String userPoolName; + private String userIdForSrp; + private String password; + private BigInteger salt; + + public SmallX(String userPoolName, String userIdForSrp, String password, BigInteger salt) { + this.userPoolName = userPoolName; + this.userIdForSrp = userIdForSrp; + this.password = password; + this.salt = salt; + } + + public BigInteger value() throws NoSuchAlgorithmException { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + String userPass = this.userPoolName + this.userIdForSrp + ":" + password; + byte[] userPassHash = digest.digest(userPass.getBytes(StandardCharsets.UTF_8)); // inner hash + + // JS の padHex 相当:符号なしの even-length hex 文字列を作成 + String saltHex = this.salt.toString(16); + if (saltHex.length() % 2 != 0) + saltHex = "0" + saltHex; + if (saltHex.matches("^[89a-fA-F].*")) + saltHex = "00" + saltHex; + byte[] saltBytes = Helper.hexStringToByteArray(saltHex); + + byte[] combined = new byte[saltBytes.length + userPassHash.length]; + System.arraycopy(saltBytes, 0, combined, 0, saltBytes.length); + System.arraycopy(userPassHash, 0, combined, saltBytes.length, userPassHash.length); + + byte[] xHash = digest.digest(combined); // final hash + return new BigInteger(1, xHash); + } +} diff --git a/src/main/java/city/makeour/moc/auth/srp/SrpAuthenticationHelper.java b/src/main/java/city/makeour/moc/auth/srp/SrpAuthenticationHelper.java new file mode 100644 index 0000000..f999cd8 --- /dev/null +++ b/src/main/java/city/makeour/moc/auth/srp/SrpAuthenticationHelper.java @@ -0,0 +1,88 @@ +package city.makeour.moc.auth.srp; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.util.Base64; +import java.util.Date; +import java.util.TimeZone; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +public class SrpAuthenticationHelper { + + private final SmallA a; + private final LargeA A; + private final LargeN N; + private final SmallG g = new SmallG(); + private final SmallK k; + private final String userPoolId; + private final String userPoolName;; + + public SrpAuthenticationHelper(String userPoolId) { + this.N = new LargeN(); + this.a = new SmallA(N); + this.A = new LargeA(N, this.a, this.g); + this.k = new SmallK(N, g); + this.userPoolId = userPoolId; + this.userPoolName = userPoolId.split("_")[1]; + } + + public String getA() { + return this.A.value().toString(16); + } + + public byte[] getPasswordAuthenticationKey( + String userIdForSrp, + String password, + String srpBHex, + String saltHex, + String secretBlock) throws InvalidKeyException, NoSuchAlgorithmException { + + LargeB B = new LargeB(srpBHex); + if (B.value().mod(this.N.value()).equals(BigInteger.ZERO)) { + throw new IllegalStateException("Invalid server B value"); + } + SmallU u = new SmallU(A, B); + BigInteger salt = new BigInteger(1, Helper.hexStringToByteArray(saltHex)); + SmallX x = new SmallX(this.userPoolName, userIdForSrp, password, salt); + LargeS S = new LargeS(B, this.k, this.g, x, this.a, u, this.N); + HKDF hkdf = new HKDF(S, u); + + return hkdf.value(); + } + + public String calculateSignature(String userIdForSRP, String secretBlock, String timestamp, byte[] key) + throws NoSuchAlgorithmException, InvalidKeyException { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(key, "HmacSHA256")); + + byte[] poolNameBytes = this.userPoolName.getBytes(StandardCharsets.UTF_8); + byte[] userIdBytes = userIdForSRP.getBytes(StandardCharsets.UTF_8); + byte[] secretBlockBytes = Base64.getDecoder().decode(secretBlock); + byte[] timestampBytes = timestamp.getBytes(StandardCharsets.UTF_8); + + byte[] message = new byte[poolNameBytes.length + userIdBytes.length + secretBlockBytes.length + + timestampBytes.length]; + int pos = 0; + System.arraycopy(poolNameBytes, 0, message, pos, poolNameBytes.length); + pos += poolNameBytes.length; + System.arraycopy(userIdBytes, 0, message, pos, userIdBytes.length); + pos += userIdBytes.length; + System.arraycopy(secretBlockBytes, 0, message, pos, secretBlockBytes.length); + pos += secretBlockBytes.length; + System.arraycopy(timestampBytes, 0, message, pos, timestampBytes.length); + + byte[] rawSignature = mac.doFinal(message); + return Base64.getEncoder().encodeToString(rawSignature); + } + + public String getCurrentFormattedTimestamp() { + SimpleDateFormat format = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", java.util.Locale.US); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + return format.format(new Date()); + } +} From 7d5768870210e27f0de3117ee5b49b1b943cb3e6 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Fri, 2 May 2025 07:26:37 +0900 Subject: [PATCH 33/36] worked --- .../moc/auth/srp/SrpAuthenticationHelper.java | 2 +- .../makeour/moc/FetchCognitoTokenTest.java | 144 ++---------------- 2 files changed, 11 insertions(+), 135 deletions(-) diff --git a/src/main/java/city/makeour/moc/auth/srp/SrpAuthenticationHelper.java b/src/main/java/city/makeour/moc/auth/srp/SrpAuthenticationHelper.java index f999cd8..d937b03 100644 --- a/src/main/java/city/makeour/moc/auth/srp/SrpAuthenticationHelper.java +++ b/src/main/java/city/makeour/moc/auth/srp/SrpAuthenticationHelper.java @@ -81,7 +81,7 @@ public String calculateSignature(String userIdForSRP, String secretBlock, String } public String getCurrentFormattedTimestamp() { - SimpleDateFormat format = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", java.util.Locale.US); + SimpleDateFormat format = new SimpleDateFormat("EEE MMM d HH:mm:ss z yyyy", java.util.Locale.US); format.setTimeZone(TimeZone.getTimeZone("UTC")); return format.format(new Date()); } diff --git a/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java b/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java index b2cc2a1..1a60b9c 100644 --- a/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java +++ b/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java @@ -1,145 +1,21 @@ package city.makeour.moc; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; -import java.lang.reflect.Field; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient; -import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthenticationResultType; -import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthRequest; -import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthResponse; -import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeRequest; -import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeResponse; class FetchCognitoTokenTest { - private static final String TEST_USER_POOL_ID = "ap-northeast-1_testpool"; - private static final String TEST_CLIENT_ID = "test-client-id"; - private static final String TEST_USERNAME = "testuser"; - private static final String TEST_PASSWORD = "testpassword"; - private static final String TEST_ID_TOKEN = "test-id-token"; - private static final String TEST_REFRESH_TOKEN = "test-refresh-token"; - - @Mock - private CognitoIdentityProviderClient mockCognitoClient; - - private FetchCognitoToken tokenFetcher; - - @BeforeEach - void setUp() throws Exception { - MockitoAnnotations.openMocks(this); - - tokenFetcher = new FetchCognitoToken(TEST_USER_POOL_ID, TEST_CLIENT_ID); - tokenFetcher.setAuthParameters(TEST_USERNAME, TEST_PASSWORD); - - // モックしたクライアントを注入 - Field clientField = FetchCognitoToken.class.getDeclaredField("client"); - clientField.setAccessible(true); - clientField.set(tokenFetcher, mockCognitoClient); - } - @Test - @DisplayName("コンストラクタで正しく初期化されることを確認") - void constructorShouldInitializeCorrectly() { - FetchCognitoToken fetcher = new FetchCognitoToken(TEST_USER_POOL_ID, TEST_CLIENT_ID); - - assertEquals(TEST_USER_POOL_ID, fetcher.cognitoUserPoolId); - assertEquals(TEST_CLIENT_ID, fetcher.cognitoClientId); - assertNotNull(fetcher.client); - } - - @Test - @DisplayName("認証パラメータが正しく設定されることを確認") - void setAuthParametersShouldSetCredentials() { - FetchCognitoToken fetcher = new FetchCognitoToken(TEST_USER_POOL_ID, TEST_CLIENT_ID); - fetcher.setAuthParameters(TEST_USERNAME, TEST_PASSWORD); - - assertEquals(TEST_USERNAME, fetcher.username); - assertEquals(TEST_PASSWORD, fetcher.password); - } - - @Test - @DisplayName("リフレッシュトークンを使用してトークンを取得できることを確認") - void fetchTokenWithRefreshTokenShouldSucceed() { - // リフレッシュトークンを設定 - tokenFetcher.refreshToken = TEST_REFRESH_TOKEN; - - // モックの応答を設定 - AuthenticationResultType authResult = AuthenticationResultType.builder() - .idToken(TEST_ID_TOKEN) - .refreshToken(TEST_REFRESH_TOKEN) - .build(); - - InitiateAuthResponse mockResponse = InitiateAuthResponse.builder() - .authenticationResult(authResult) - .build(); - - when(mockCognitoClient.initiateAuth(any(InitiateAuthRequest.class))) - .thenReturn(mockResponse); - - // トークン取得を実行 - String token = tokenFetcher.fetchToken(); - - assertEquals(TEST_ID_TOKEN, token); - assertEquals(TEST_REFRESH_TOKEN, tokenFetcher.refreshToken); - } - - @Test - @DisplayName("SRP認証でトークンを取得できることを確認") - void fetchTokenWithSRPShouldSucceed() { - // InitiateAuth のモック応答を設定 - InitiateAuthResponse initAuthResponse = InitiateAuthResponse.builder() - .challengeName("PASSWORD_VERIFIER") - .challengeParameters(new java.util.HashMap() { - { - put("USER_ID_FOR_SRP", TEST_USERNAME); - put("SRP_B", "123456789abcdef"); - put("SALT", "salt123"); - put("SECRET_BLOCK", "c2VjcmV0"); // Base64エンコードされた "secret" - } - }) - .build(); - - when(mockCognitoClient.initiateAuth(any(InitiateAuthRequest.class))) - .thenReturn(initAuthResponse); - - // RespondToAuthChallenge のモック応答を設定 - AuthenticationResultType authResult = AuthenticationResultType.builder() - .idToken(TEST_ID_TOKEN) - .refreshToken(TEST_REFRESH_TOKEN) - .build(); - - RespondToAuthChallengeResponse challengeResponse = RespondToAuthChallengeResponse.builder() - .authenticationResult(authResult) - .build(); - - when(mockCognitoClient.respondToAuthChallenge(any(RespondToAuthChallengeRequest.class))) - .thenReturn(challengeResponse); - - // トークン取得を実行 - String token = tokenFetcher.fetchToken(); - - assertEquals(TEST_ID_TOKEN, token); - assertEquals(TEST_REFRESH_TOKEN, tokenFetcher.refreshToken); - } - - @Test - @DisplayName("SRP認証でトークン取得のテスト") - void testFetchTokenWithSRP() { - FetchCognitoToken fetcher = new FetchCognitoToken( - "ap-northeast-1_nXSBLO7v6", - "3d7d0piq75halieshbi7o8keca"); - - fetcher.setAuthParameters("ushio.s@gmail.com", ""); - fetcher.fetchToken(); + public void testFetchTokenWithSrpAuth() throws Exception { + String cognitoUserPoolId = "ap-northeast-1_nXSBLO7v6"; + String cognitoClientId = "3d7d0piq75halieshbi7o8keca"; + String username = "ushio.s@gmail.com"; + String password = ""; + + FetchCognitoToken fetchCognitoToken = new FetchCognitoToken(cognitoUserPoolId, cognitoClientId); + fetchCognitoToken.setAuthParameters(username, password); + String token = fetchCognitoToken.fetchTokenWithSrpAuth(); + assertNotNull(token); } } \ No newline at end of file From ab8107b3308643427557fb4e9bfdcfc8cc2c806c Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sat, 3 May 2025 00:05:46 +0900 Subject: [PATCH 34/36] worked --- src/main/java/city/makeour/moc/auth/srp/Helper.java | 8 ++++++++ src/main/java/city/makeour/moc/auth/srp/SmallA.java | 4 +++- .../makeour/moc/auth/srp/SrpAuthenticationHelper.java | 11 +++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main/java/city/makeour/moc/auth/srp/Helper.java b/src/main/java/city/makeour/moc/auth/srp/Helper.java index 1251617..97884dd 100644 --- a/src/main/java/city/makeour/moc/auth/srp/Helper.java +++ b/src/main/java/city/makeour/moc/auth/srp/Helper.java @@ -44,4 +44,12 @@ public static byte[] hexStringToByteArray(String s) { } return data; } + + public static String toHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } } diff --git a/src/main/java/city/makeour/moc/auth/srp/SmallA.java b/src/main/java/city/makeour/moc/auth/srp/SmallA.java index ae4ba17..25062c7 100644 --- a/src/main/java/city/makeour/moc/auth/srp/SmallA.java +++ b/src/main/java/city/makeour/moc/auth/srp/SmallA.java @@ -5,13 +5,15 @@ public class SmallA { private LargeN N; + private final BigInteger a; public SmallA(LargeN N) { this.N = N; + a = new BigInteger(1, generateRandomBytes(128)).mod(this.N.value()); } public BigInteger value() { - return new BigInteger(1, generateRandomBytes(128)).mod(this.N.value()); + return a; } private static byte[] generateRandomBytes(int size) { diff --git a/src/main/java/city/makeour/moc/auth/srp/SrpAuthenticationHelper.java b/src/main/java/city/makeour/moc/auth/srp/SrpAuthenticationHelper.java index d937b03..d2cae9e 100644 --- a/src/main/java/city/makeour/moc/auth/srp/SrpAuthenticationHelper.java +++ b/src/main/java/city/makeour/moc/auth/srp/SrpAuthenticationHelper.java @@ -35,6 +35,11 @@ public String getA() { return this.A.value().toString(16); } + public String getHexA() { + String hex = A.value().toString(16); + return String.format("%0256x", new BigInteger(hex, 16)); // 256桁になるように0埋め + } + public byte[] getPasswordAuthenticationKey( String userIdForSrp, String password, @@ -76,6 +81,12 @@ public String calculateSignature(String userIdForSRP, String secretBlock, String pos += secretBlockBytes.length; System.arraycopy(timestampBytes, 0, message, pos, timestampBytes.length); + String p = new String(poolNameBytes); + String u = new String(userIdBytes); + String s = new String(secretBlockBytes); + String t = new String(timestampBytes); + String hm = Helper.toHex(message); + byte[] rawSignature = mac.doFinal(message); return Base64.getEncoder().encodeToString(rawSignature); } From 2dd5fd4fad70af8d37513da3ddb691289c4d0e59 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sat, 3 May 2025 00:39:07 +0900 Subject: [PATCH 35/36] worked --- .../makeour/moc/AuthenticationHelper.java | 275 ------------- .../makeour/moc/CognitoAuthenticator.java | 87 ----- src/main/java/city/makeour/moc/Exec.java | 364 ------------------ .../city/makeour/moc/FetchCognitoToken.java | 6 +- .../makeour/moc/JavaCognitoLoginSample.java | 82 ---- src/main/java/city/makeour/moc/MocClient.java | 7 +- .../city/makeour/moc/RefreshTokenStorage.java | 22 ++ .../moc/RefreshTokenStorageInterface.java | 10 + src/main/java/city/makeour/moc/Token.java | 19 + .../makeour/moc/TokenFetcherInterface.java | 2 +- .../moc/auth/srp/SrpAuthenticationHelper.java | 8 - .../moc/CognitoAuthenticationHelperTest.java | 39 -- .../makeour/moc/FetchCognitoTokenTest.java | 19 +- 13 files changed, 74 insertions(+), 866 deletions(-) delete mode 100644 src/main/java/city/makeour/moc/AuthenticationHelper.java delete mode 100644 src/main/java/city/makeour/moc/CognitoAuthenticator.java delete mode 100644 src/main/java/city/makeour/moc/Exec.java delete mode 100644 src/main/java/city/makeour/moc/JavaCognitoLoginSample.java create mode 100644 src/main/java/city/makeour/moc/RefreshTokenStorage.java create mode 100644 src/main/java/city/makeour/moc/RefreshTokenStorageInterface.java create mode 100644 src/main/java/city/makeour/moc/Token.java delete mode 100644 src/test/java/city/makeour/moc/CognitoAuthenticationHelperTest.java diff --git a/src/main/java/city/makeour/moc/AuthenticationHelper.java b/src/main/java/city/makeour/moc/AuthenticationHelper.java deleted file mode 100644 index 5848279..0000000 --- a/src/main/java/city/makeour/moc/AuthenticationHelper.java +++ /dev/null @@ -1,275 +0,0 @@ -package city.makeour.moc; - -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.SecureRandom; -import java.text.SimpleDateFormat; -import java.util.Base64; -import java.util.Date; -import java.util.Locale; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -public class AuthenticationHelper { - - private static final String HEX_N = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + - "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + - "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + - "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + - "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + - "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + - "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + - "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF"; - - private static final BigInteger N = new BigInteger(HEX_N, 16); - private static final BigInteger g = BigInteger.valueOf(2); - private static final BigInteger k; - - static { - try { - // MessageDigest digest = MessageDigest.getInstance("SHA-256"); - // digest.update(padHex(N)); - // digest.update(padHex(g)); - k = calculateK(N, g); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private final SecureRandom random; - private final BigInteger a; - private final BigInteger A; - private final String poolName; - - public AuthenticationHelper(String userPoolId) { - random = new SecureRandom(); - a = new BigInteger(1, generateRandomBytes(128)).mod(N); - A = g.modPow(a, N); - poolName = userPoolId.split("_", 2)[1]; - } - - public BigInteger getA() { - return A; - } - - public String getHexA() { - - String hex = A.toString(16); - return String.format("%0256x", new BigInteger(hex, 16)); // 256桁になるように0埋め - } - - private static byte[] generateRandomBytes(int size) { - byte[] bytes = new byte[size]; - new java.security.SecureRandom().nextBytes(bytes); - return bytes; - } - - public byte[] getPasswordAuthenticationKey( - String poolName, String username, String password, String srpBHex, String saltHex, String secretBlock) { - - BigInteger B = new BigInteger(1, hexStringToByteArray(srpBHex)); - if (B.mod(N).equals(BigInteger.ZERO)) { - throw new IllegalStateException("Invalid server B value"); - } - - BigInteger u = computeU(A, B); - BigInteger salt = new BigInteger(1, hexStringToByteArray(saltHex)); - BigInteger x = calculateX(poolName, username, password, salt); - - // BigInteger base = B.subtract(k.multiply(g.modPow(x, N))).mod(N); - // BigInteger exp = a.add(u.multiply(x)); - BigInteger S = calculateS(B, k, g, x, a, u, N); - - return computeHKDF(padHex(S), u.toByteArray()); - } - - private static byte[] hexStringToByteArray(String s) { - int len = s.length(); - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) - + Character.digit(s.charAt(i + 1), 16)); - } - return data; - } - - public String calculateSignature(String userIdForSRP, String secretBlock, String timestamp, byte[] key) { - try { - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeySpec(key, "HmacSHA256")); - - byte[] poolNameBytes = poolName.getBytes(StandardCharsets.UTF_8); - byte[] userIdBytes = userIdForSRP.getBytes(StandardCharsets.UTF_8); - byte[] secretBlockBytes = Base64.getDecoder().decode(secretBlock); - byte[] timestampBytes = timestamp.getBytes(StandardCharsets.UTF_8); - - byte[] message = new byte[poolNameBytes.length + userIdBytes.length + secretBlockBytes.length - + timestampBytes.length]; - - int pos = 0; - System.arraycopy(poolNameBytes, 0, message, pos, poolNameBytes.length); - pos += poolNameBytes.length; - System.arraycopy(userIdBytes, 0, message, pos, userIdBytes.length); - pos += userIdBytes.length; - System.arraycopy(secretBlockBytes, 0, message, pos, secretBlockBytes.length); - pos += secretBlockBytes.length; - System.arraycopy(timestampBytes, 0, message, pos, timestampBytes.length); - - byte[] rawSignature = mac.doFinal(message); - - return Base64.getEncoder().encodeToString(rawSignature); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static String bytesToHex(byte[] bytes) { - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - sb.append(String.format("%02x", b)); - } - return sb.toString(); - } - - private static BigInteger calculateS(BigInteger B, BigInteger k, BigInteger g, BigInteger x, BigInteger a, - BigInteger u, BigInteger N) { - // Step1: base = (B - k * g^x) mod N - BigInteger gModPowX = g.modPow(x, N); - BigInteger kgx = k.multiply(gModPowX).mod(N); - BigInteger base = B.subtract(kgx).mod(N); - - // Step2: exponent = (a + u * x) - BigInteger exp = a.add(u.multiply(x)); - - // Step3: S = base ^ exp mod N - BigInteger S = base.modPow(exp, N); - - return S; - } - - public String getCurrentFormattedTimestamp() { - SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM d HH:mm:ss 'UTC' yyyy", Locale.US); - sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); - return sdf.format(new Date()); - } - - // --- 内部ヘルパーメソッド --- - - private static byte[] padHex(BigInteger n) { - byte[] hex = n.toByteArray(); - if (hex.length == 256) { - return hex; - } - byte[] padded = new byte[256]; - if (hex.length > 256) { - // Remove leading 0 byte (sign byte) - System.arraycopy(hex, hex.length - 256, padded, 0, 256); - } else { - System.arraycopy(hex, 0, padded, 256 - hex.length, hex.length); - } - return padded; - } - - private static BigInteger computeU(BigInteger A, BigInteger B) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - - byte[] aPadded = padHex(A); - byte[] bPadded = padHex(B); - - byte[] combined = new byte[aPadded.length + bPadded.length]; - System.arraycopy(aPadded, 0, combined, 0, aPadded.length); - System.arraycopy(bPadded, 0, combined, aPadded.length, bPadded.length); - - byte[] uHash = digest.digest(combined); - - return new BigInteger(1, uHash); // 必ず符号なし - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static BigInteger calculateX(String poolName, String username, String password, BigInteger salt) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - - // Step1: poolName + username + ":" + password を SHA-256 - String userPass = poolName + username + ":" + password; - byte[] userPassHash = digest.digest(userPass.getBytes(StandardCharsets.UTF_8)); - - // Step2: saltバイト列をpadして + userPassHash を SHA-256 - digest.reset(); - byte[] saltPadded = padHex(salt); - - byte[] combined = new byte[saltPadded.length + userPassHash.length]; - System.arraycopy(saltPadded, 0, combined, 0, saltPadded.length); - System.arraycopy(userPassHash, 0, combined, saltPadded.length, userPassHash.length); - - byte[] xHash = digest.digest(combined); - - return new BigInteger(1, xHash); // 必ず符号なし - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static BigInteger calculateK(BigInteger N, BigInteger g) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - - byte[] nPadded = padHex(N); // さっき作ったやつ - byte[] gPadded = padHex(g); - - // 連結 - byte[] ng = new byte[nPadded.length + gPadded.length]; - System.arraycopy(nPadded, 0, ng, 0, nPadded.length); - System.arraycopy(gPadded, 0, ng, nPadded.length, gPadded.length); - - byte[] hash = digest.digest(ng); - - return new BigInteger(1, hash); // 必ず符号なし! - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static byte[] computeHKDF(byte[] ikm, byte[] salt) { - try { - Mac mac = Mac.getInstance("HmacSHA256"); - - // Step1: PRK = HMAC(salt, ikm) - mac.init(new SecretKeySpec(salt, "HmacSHA256")); - byte[] prk = mac.doFinal(ikm); - - // Step2: OKM = HMAC(prk, info) - mac.init(new SecretKeySpec(prk, "HmacSHA256")); - byte[] info = "Caldera Derived Key".getBytes(StandardCharsets.UTF_8); - byte[] okm = mac.doFinal(info); - - // Step3: 最初の16バイトだけ取り出す - byte[] result = new byte[16]; - System.arraycopy(okm, 0, result, 0, 16); - - return result; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static byte[] concatBytes(byte[]... arrays) { - int totalLength = 0; - for (byte[] array : arrays) { - totalLength += array.length; - } - - byte[] result = new byte[totalLength]; - int pos = 0; - for (byte[] array : arrays) { - System.arraycopy(array, 0, result, pos, array.length); - pos += array.length; - } - return result; - } -} diff --git a/src/main/java/city/makeour/moc/CognitoAuthenticator.java b/src/main/java/city/makeour/moc/CognitoAuthenticator.java deleted file mode 100644 index 18a3668..0000000 --- a/src/main/java/city/makeour/moc/CognitoAuthenticator.java +++ /dev/null @@ -1,87 +0,0 @@ -package city.makeour.moc; - -import java.util.HashMap; -import java.util.Map; - -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient; -import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthFlowType; -import software.amazon.awssdk.services.cognitoidentityprovider.model.ChallengeNameType; -import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthRequest; -import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthResponse; -import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeRequest; -import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeResponse; - -public class CognitoAuthenticator { - - private final CognitoIdentityProviderClient cognitoClient; - private final String clientId; - private final String userPoolId; - - public CognitoAuthenticator(Region region, String clientId, String userPoolId) { - this.cognitoClient = CognitoIdentityProviderClient.builder() - .region(region) - .build(); - this.clientId = clientId; - this.userPoolId = userPoolId; - } - - public String login(String username, String password) { - AuthenticationHelper authHelper = new AuthenticationHelper(userPoolId); - - // ステップ1: SRP_A計算 - String srpAString = authHelper.getA().toString(16); - - // ステップ2: InitiateAuthで認証スタート - Map initAuthParams = new HashMap<>(); - initAuthParams.put("USERNAME", username); - initAuthParams.put("SRP_A", srpAString); - - InitiateAuthRequest initiateAuthRequest = InitiateAuthRequest.builder() - .authFlow(AuthFlowType.USER_SRP_AUTH) - .clientId(clientId) - .authParameters(initAuthParams) - .build(); - - InitiateAuthResponse initiateAuthResponse = cognitoClient.initiateAuth(initiateAuthRequest); - - if (!ChallengeNameType.PASSWORD_VERIFIER.toString().equals(initiateAuthResponse.challengeNameAsString())) { - throw new RuntimeException("Unexpected challenge: " + initiateAuthResponse.challengeNameAsString()); - } - - // ステップ3: チャレンジ受け取り - Map challengeParameters = initiateAuthResponse.challengeParameters(); - - String salt = challengeParameters.get("SALT"); - String srpB = challengeParameters.get("SRP_B"); - String secretBlock = challengeParameters.get("SECRET_BLOCK"); - String userIdForSRP = challengeParameters.get("USER_ID_FOR_SRP"); - - // ステップ4: パスワード認証キー計算 - byte[] passwordAuthKey = authHelper.getPasswordAuthenticationKey(userPoolId, username, password, srpB, salt, - secretBlock); - - // ステップ5: 署名生成 - String timestamp = authHelper.getCurrentFormattedTimestamp(); - String signature = authHelper.calculateSignature(userIdForSRP, secretBlock, timestamp, passwordAuthKey); - - // ステップ6: RespondToAuthChallenge - Map challengeResponses = new HashMap<>(); - challengeResponses.put("PASSWORD_CLAIM_SECRET_BLOCK", secretBlock); - challengeResponses.put("PASSWORD_CLAIM_SIGNATURE", signature); - challengeResponses.put("TIMESTAMP", timestamp); - challengeResponses.put("USERNAME", userIdForSRP); - - RespondToAuthChallengeRequest challengeRequest = RespondToAuthChallengeRequest.builder() - .challengeName(ChallengeNameType.PASSWORD_VERIFIER) - .clientId(clientId) - .session(initiateAuthResponse.session()) - .challengeResponses(challengeResponses) - .build(); - - RespondToAuthChallengeResponse challengeResponse = cognitoClient.respondToAuthChallenge(challengeRequest); - - // ステップ7: 成功! IDToken取得 - return challengeResponse.authenticationResult().idToken(); - } -} diff --git a/src/main/java/city/makeour/moc/Exec.java b/src/main/java/city/makeour/moc/Exec.java deleted file mode 100644 index d844c91..0000000 --- a/src/main/java/city/makeour/moc/Exec.java +++ /dev/null @@ -1,364 +0,0 @@ -package city.makeour.moc; - -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Base64; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.TimeZone; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient; -import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthFlowType; -import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthenticationResultType; -import software.amazon.awssdk.services.cognitoidentityprovider.model.ChallengeNameType; -import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthRequest; -import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthResponse; -import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeRequest; -import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeResponse; - -public class Exec { - public static void main(String[] args) { - String userPoolId = "ap-northeast-1_nXSBLO7v6"; // ← ここあなたの設定 - String userPoolName = "nXSBLO7v6"; - String clientId = "3d7d0piq75halieshbi7o8keca"; // ← ここあなたの設定 - String username = "ushio.s@gmail.com"; // ← ここあなたの設定 - String password = ""; // ← ここあなたの設定 - Region region = Region.AP_NORTHEAST_1; - - AuthenticationHelper helper = new AuthenticationHelper(userPoolId); - - CognitoIdentityProviderClient cognitoClient = CognitoIdentityProviderClient.builder() - .region(region) - .credentialsProvider(DefaultCredentialsProvider.create()) - .build(); - - try { - InitiateAuthRequest initiateAuthRequest = InitiateAuthRequest.builder() - .authFlow(AuthFlowType.USER_SRP_AUTH) - .clientId(clientId) - .authParameters(Map.of( - "USERNAME", username, - "SRP_A", helper.getA().toString(16))) - .build(); - - System.out.println("SRP_A = " + helper.getHexA()); - - InitiateAuthResponse initiateAuthResponse = cognitoClient.initiateAuth(initiateAuthRequest); - Map challengeParameters = initiateAuthResponse.challengeParameters(); - - String userIdForSrp = challengeParameters.get("USER_ID_FOR_SRP"); - String salt = challengeParameters.get("SALT"); - String srpB = challengeParameters.get("SRP_B"); - String secretBlock = challengeParameters.get("SECRET_BLOCK"); - - // ← ここで一度ログ出力して比較用データを取得 - System.out.println("userIdForSrp = " + userIdForSrp); - System.out.println("salt = " + salt); - System.out.println("srpB = " + srpB); - System.out.println("secretBlock = " + secretBlock); - - byte[] signatureKey = helper.getPasswordAuthenticationKey(userPoolName, userIdForSrp, password, srpB, salt, - secretBlock); - String timestamp = helper.getCurrentFormattedTimestamp(); - String signature = helper.calculateSignature(userIdForSrp, secretBlock, timestamp, signatureKey); - - // ← signature計算結果も出力 - System.out.println("timestamp = " + timestamp); - System.out.println("signature = " + signature); - - Map challengeResponses = new HashMap<>(); - challengeResponses.put("USERNAME", userIdForSrp); - challengeResponses.put("PASSWORD_CLAIM_SECRET_BLOCK", secretBlock); - challengeResponses.put("PASSWORD_CLAIM_SIGNATURE", signature); - challengeResponses.put("TIMESTAMP", timestamp); - - System.out.println("challengeResponses = " + challengeResponses); - - RespondToAuthChallengeRequest respondRequest = RespondToAuthChallengeRequest.builder() - .challengeName(ChallengeNameType.PASSWORD_VERIFIER) - .clientId(clientId) - .challengeResponses(challengeResponses) - .build(); - - RespondToAuthChallengeResponse authChallengeResponse = cognitoClient.respondToAuthChallenge(respondRequest); - AuthenticationResultType authResult = authChallengeResponse.authenticationResult(); - - System.out.println("ID Token:"); - System.out.println(authResult.idToken()); - } catch (Exception e) { - e.printStackTrace(); - } finally { - cognitoClient.close(); - } - } - - public static class AuthenticationHelper { - private static final BigInteger N = new BigInteger(1, hexStringToByteArray( - "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD" - + - "3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F" - + - "24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552" - + - "BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF0" - + - "6F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64EC" - + - "FB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A" - + - "0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D1" - + - "20A93AD2CAFFFFFFFFFFFFFFFF")); - private static final BigInteger g = BigInteger.valueOf(2); - private static final BigInteger k = calculateK(N, g); - - private final BigInteger a; - private final BigInteger A; - private final String poolName; - - public AuthenticationHelper(String userPoolId) { - this.poolName = userPoolId; - // this.a = new BigInteger(1, generateRandomBytes(128)).mod(N); - this.a = new BigInteger("123456789abcdef123456789abcdef123456789abcdef", 16); - this.A = g.modPow(a, N); - } - - public BigInteger getA() { - return A; - } - - public String getHexA() { - String hex = A.toString(16); - return String.format("%0256x", new BigInteger(hex, 16)); // 256桁になるように0埋め - } - - private static byte[] generateRandomBytes(int size) { - byte[] bytes = new byte[size]; - new java.security.SecureRandom().nextBytes(bytes); - return bytes; - } - - public byte[] getPasswordAuthenticationKey(String poolName, String username, String password, String srpBHex, - String saltHex, - String secretBlock) { - // BigInteger B = new BigInteger(1, hexStringToByteArray(srpBHex)); - // srpBHex = - // "6472ea13e9cd1f054fb17c211f26831c47a615ca8c42cdab2893c1a015965c658b93c8324e90436eb6c6cf730ffd7db2200f716b74e340f9b2c02654362f99f1f78c610f6c0d4bc4996963063e4ca6af87f3516536fda0b0116695ec7a564ec9992f1b6e862e86c4f326d205d3c1accad76e41eca1e5a83ffdf8a58b3c5673f21a26f5cbc8452bf84945b5898ddb5a4b120ff09de4a7a63cbd7ec11f9601b90735c65b02075054e448a64ba7ebf826682e1a1e92990714b8725862bb6a6f0b1eb803ad13fddcac087a8b79a2402fc15421261caaad1db51fc3133469ef5db789af25baddb513986d5e5555683fe5d47f2e0c0e9eb048e81ff3dc372a9b862345f0a4ab0818f72d89f464959f22795b38347627e3fee375ac8fbc51ab8d31e438dac6926c7b1c690d32c5c6a75ee37e84886973d18cb8dcb75c3e835007df6ddcc91d13e4e99b6e291d7e9260ddcfbee8620ddc2f9fc7406369c5d085cc3ceba7207b376df8806a88c3a3ff404eba276298edb936111b41d0f9042c07581dd0e1"; - BigInteger B = new BigInteger(1, hexStringToByteArray(srpBHex)); - if (B.mod(N).equals(BigInteger.ZERO)) { - throw new IllegalStateException("Invalid server B value"); - } - BigInteger u = computeU(A, B); - System.out.println("Java U = " + u.toString(16)); - BigInteger salt = new BigInteger(1, hexStringToByteArray(saltHex)); - BigInteger x = calculateX(poolName, username, password, salt); - System.out.println("Java x = " + x.toString(16)); - BigInteger S = calculateS(B, k, g, x, a, u, N); - System.out.println("Java S = " + S.toString(16)); - byte[] signatureKey = computeHKDF(padHex(S), u.toByteArray()); - System.out.println("Java K = " + Base64.getEncoder().encodeToString(signatureKey)); - - System.out.println("Java N = " + N.toString(16)); - return signatureKey; - } - - public String calculateSignature(String userIdForSRP, String secretBlock, String timestamp, byte[] key) { - try { - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeySpec(key, "HmacSHA256")); - - byte[] poolNameBytes = poolName.split("_")[1].getBytes(StandardCharsets.UTF_8); - byte[] userIdBytes = userIdForSRP.getBytes(StandardCharsets.UTF_8); - byte[] secretBlockBytes = Base64.getDecoder().decode(secretBlock); - byte[] timestampBytes = timestamp.getBytes(StandardCharsets.UTF_8); - - byte[] message = new byte[poolNameBytes.length + userIdBytes.length + secretBlockBytes.length - + timestampBytes.length]; - int pos = 0; - System.arraycopy(poolNameBytes, 0, message, pos, poolNameBytes.length); - pos += poolNameBytes.length; - System.arraycopy(userIdBytes, 0, message, pos, userIdBytes.length); - pos += userIdBytes.length; - System.arraycopy(secretBlockBytes, 0, message, pos, secretBlockBytes.length); - pos += secretBlockBytes.length; - System.arraycopy(timestampBytes, 0, message, pos, timestampBytes.length); - - System.out.println("Java message (userPoolName) = " + new String(poolNameBytes)); - System.out.println("Java message (username) = " + new String(userIdBytes)); - System.out.println( - "Java message (secretBlock) = " + Base64.getEncoder().encodeToString(secretBlockBytes)); - System.out.println("Java message (timestamp) = " + new String(timestampBytes)); - System.out.println("Java message (hex) = " + toHex(message)); - - byte[] rawSignature = mac.doFinal(message); - return Base64.getEncoder().encodeToString(rawSignature); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public String getCurrentFormattedTimestamp() { - SimpleDateFormat format = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", java.util.Locale.US); - format.setTimeZone(TimeZone.getTimeZone("UTC")); - return format.format(new Date()); - } - - private static BigInteger calculateK(BigInteger N, BigInteger g) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - byte[] nPadded = padHex(N); - byte[] gPadded = padHex(g); - byte[] ng = new byte[nPadded.length + gPadded.length]; - System.arraycopy(nPadded, 0, ng, 0, nPadded.length); - System.arraycopy(gPadded, 0, ng, nPadded.length, gPadded.length); - byte[] hash = digest.digest(ng); - return new BigInteger(1, hash); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static BigInteger calculateX(String poolName, String username, String password, BigInteger salt) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - String userPass = poolName + username + ":" + password; - byte[] userPassHash = digest.digest(userPass.getBytes(StandardCharsets.UTF_8)); // inner hash - - // JS の padHex 相当:符号なしの even-length hex 文字列を作成 - String saltHex = salt.toString(16); - if (saltHex.length() % 2 != 0) - saltHex = "0" + saltHex; - if (saltHex.matches("^[89a-fA-F].*")) - saltHex = "00" + saltHex; - byte[] saltBytes = hexStringToByteArray(saltHex); - - byte[] combined = new byte[saltBytes.length + userPassHash.length]; - System.arraycopy(saltBytes, 0, combined, 0, saltBytes.length); - System.arraycopy(userPassHash, 0, combined, saltBytes.length, userPassHash.length); - - byte[] xHash = digest.digest(combined); // final hash - return new BigInteger(1, xHash); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static BigInteger computeU(BigInteger A, BigInteger B) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - byte[] aPadded = padHex(A); - byte[] bPadded = padHex(B); - System.out.println("padHex(A) = " + toHex(padHex(A))); - System.out.println("padHex(B) = " + toHex(padHex(B))); - byte[] combined = new byte[aPadded.length + bPadded.length]; - System.arraycopy(aPadded, 0, combined, 0, aPadded.length); - System.arraycopy(bPadded, 0, combined, aPadded.length, bPadded.length); - byte[] uHash = digest.digest(combined); - return new BigInteger(1, uHash); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static String toHex(byte[] bytes) { - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - sb.append(String.format("%02x", b)); - } - return sb.toString(); - } - - private static BigInteger calculateS(BigInteger B, BigInteger k, BigInteger g, BigInteger x, BigInteger a, - BigInteger u, BigInteger N) { - BigInteger gModPowX = g.modPow(x, N); - BigInteger kgx = k.multiply(gModPowX).mod(N); - BigInteger base = B.subtract(kgx).mod(N); - BigInteger exp = a.add(u.multiply(x)); - return base.modPow(exp, N); - } - - private static byte[] computeHKDF(byte[] ikm, byte[] salt) { - try { - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeySpec(salt, "HmacSHA256")); - byte[] prk = mac.doFinal(ikm); - - mac.init(new SecretKeySpec(prk, "HmacSHA256")); - byte[] info = "Caldera Derived Key".getBytes(StandardCharsets.UTF_8); - byte[] infoWithCounter = new byte[info.length + 1]; - System.arraycopy(info, 0, infoWithCounter, 0, info.length); - infoWithCounter[info.length] = 0x01; - - byte[] okm = mac.doFinal(infoWithCounter); - return Arrays.copyOfRange(okm, 0, 16); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static byte[] padHexOld(BigInteger n) { - byte[] hex = n.toByteArray(); - if (hex.length == 256) { - return hex; - } - byte[] padded = new byte[256]; - if (hex.length > 256) { - System.arraycopy(hex, hex.length - 256, padded, 0, 256); - } else { - System.arraycopy(hex, 0, padded, 256 - hex.length, hex.length); - } - return padded; - } - - private static byte[] padHex(BigInteger bigInt) { - boolean isNegative = bigInt.signum() < 0; - String hex = bigInt.abs().toString(16); - - // 奇数桁なら先頭に 0 を追加 - if (hex.length() % 2 != 0) { - hex = "0" + hex; - } - - // MSBが立っていれば "00" を先頭に追加(符号ビットを避ける) - if (hex.matches("^[89a-fA-F].*")) { - hex = "00" + hex; - } - - if (isNegative) { - // 補数計算: 反転 → +1 - StringBuilder flipped = new StringBuilder(); - for (char c : hex.toCharArray()) { - int nibble = ~Character.digit(c, 16) & 0xF; - flipped.append(Integer.toHexString(nibble)); - } - BigInteger flippedBigInt = new BigInteger(flipped.toString(), 16).add(BigInteger.ONE); - hex = flippedBigInt.toString(16); - - // MSBがFF8〜なら短縮される→不要(JSでも無視できるレベル) - if (hex.length() % 2 != 0) - hex = "0" + hex; - } - - return hexStringToByteArray(hex); - } - - private static byte[] hexStringToByteArray(String s) { - int len = s.length(); - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) - + Character.digit(s.charAt(i + 1), 16)); - } - return data; - } - } -} diff --git a/src/main/java/city/makeour/moc/FetchCognitoToken.java b/src/main/java/city/makeour/moc/FetchCognitoToken.java index 0cef83f..9be8b45 100644 --- a/src/main/java/city/makeour/moc/FetchCognitoToken.java +++ b/src/main/java/city/makeour/moc/FetchCognitoToken.java @@ -50,7 +50,7 @@ public void setAuthParameters(String username, String password) { this.password = password; } - public String fetchTokenWithSrpAuth() throws InvalidKeyException, NoSuchAlgorithmException { + public Token fetchTokenWithSrpAuth() throws InvalidKeyException, NoSuchAlgorithmException { InitiateAuthRequest initiateAuthRequest = InitiateAuthRequest.builder() .authFlow(AuthFlowType.USER_SRP_AUTH) .clientId(this.cognitoClientId) @@ -87,10 +87,10 @@ public String fetchTokenWithSrpAuth() throws InvalidKeyException, NoSuchAlgorith RespondToAuthChallengeResponse authChallengeResponse = cognitoClient.respondToAuthChallenge(respondRequest); AuthenticationResultType authResult = authChallengeResponse.authenticationResult(); - return authResult.idToken(); + return new Token(authResult.idToken(), authResult.refreshToken()); } - public String fetchToken() throws InvalidKeyException, NoSuchAlgorithmException { + public Token fetchToken() throws InvalidKeyException, NoSuchAlgorithmException { return this.fetchTokenWithSrpAuth(); } } diff --git a/src/main/java/city/makeour/moc/JavaCognitoLoginSample.java b/src/main/java/city/makeour/moc/JavaCognitoLoginSample.java deleted file mode 100644 index 4f90352..0000000 --- a/src/main/java/city/makeour/moc/JavaCognitoLoginSample.java +++ /dev/null @@ -1,82 +0,0 @@ -package city.makeour.moc; - -import java.util.HashMap; -import java.util.Map; - -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient; -import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthFlowType; -import software.amazon.awssdk.services.cognitoidentityprovider.model.ChallengeNameType; -import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthRequest; -import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthResponse; -import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeRequest; -import software.amazon.awssdk.services.cognitoidentityprovider.model.RespondToAuthChallengeResponse; - -public class JavaCognitoLoginSample { - - public static void main(String[] args) { - // あなたの設定 - String userPoolId = "ap-northeast-1_nXSBLO7v6"; - String clientId = "3d7d0piq75halieshbi7o8keca"; - String username = "ushio.s@gmail.com"; // ここはUUIDじゃない、メールアドレス! - String password = ""; - - // クライアント作成 - CognitoIdentityProviderClient client = CognitoIdentityProviderClient.builder() - .region(Region.AP_NORTHEAST_1) - .build(); - - // SRP認証用ヘルパー - AuthenticationHelper authHelper = new AuthenticationHelper(userPoolId); - - // SRP_A送信 - Map initAuthParams = new HashMap<>(); - initAuthParams.put("USERNAME", username); - initAuthParams.put("SRP_A", authHelper.getA().toString(16)); - - InitiateAuthRequest initiateAuthRequest = InitiateAuthRequest.builder() - .authFlow(AuthFlowType.USER_SRP_AUTH) - .clientId(clientId) - .authParameters(initAuthParams) - .build(); - - InitiateAuthResponse initiateAuthResponse = client.initiateAuth(initiateAuthRequest); - - if (!ChallengeNameType.PASSWORD_VERIFIER.toString().equals(initiateAuthResponse.challengeNameAsString())) { - throw new RuntimeException("Unexpected challenge: " + initiateAuthResponse.challengeNameAsString()); - } - - Map challengeParams = initiateAuthResponse.challengeParameters(); - String salt = challengeParams.get("SALT"); - String srpB = challengeParams.get("SRP_B"); - String secretBlock = challengeParams.get("SECRET_BLOCK"); - String userIdForSRP = challengeParams.get("USER_ID_FOR_SRP"); - - // SRP計算 - byte[] passwordKey = authHelper.getPasswordAuthenticationKey(userPoolId, username, password, srpB, salt, - secretBlock); - String timestamp = authHelper.getCurrentFormattedTimestamp(); - String signature = authHelper.calculateSignature(userIdForSRP, secretBlock, timestamp, passwordKey); - - // チャレンジ応答 - Map challengeResponses = new HashMap<>(); - challengeResponses.put("PASSWORD_CLAIM_SECRET_BLOCK", secretBlock); - challengeResponses.put("PASSWORD_CLAIM_SIGNATURE", signature); - challengeResponses.put("TIMESTAMP", timestamp); - challengeResponses.put("USERNAME", username); - - RespondToAuthChallengeRequest challengeRequest = RespondToAuthChallengeRequest.builder() - .challengeName(ChallengeNameType.PASSWORD_VERIFIER) - .clientId(clientId) - .session(initiateAuthResponse.session()) - .challengeResponses(challengeResponses) - .build(); - - RespondToAuthChallengeResponse authChallengeResponse = client.respondToAuthChallenge(challengeRequest); - - // 成功したらIDトークン表示! - String idToken = authChallengeResponse.authenticationResult().idToken(); - System.out.println("ID Token:"); - System.out.println(idToken); - } -} diff --git a/src/main/java/city/makeour/moc/MocClient.java b/src/main/java/city/makeour/moc/MocClient.java index fb76b46..14526a9 100644 --- a/src/main/java/city/makeour/moc/MocClient.java +++ b/src/main/java/city/makeour/moc/MocClient.java @@ -13,6 +13,8 @@ public class MocClient { protected TokenFetcherInterface tokenFetcher; + protected RefreshTokenStorageInterface refreshTokenStorage; + public MocClient() { this("https://orion.sandbox.makeour.city"); } @@ -22,6 +24,7 @@ public MocClient(String basePath) { this.apiClient.setBasePath(basePath); this.entitiesApi = new EntitiesApi(this.apiClient); + this.refreshTokenStorage = new RefreshTokenStorage(); } public EntitiesApi entities() { @@ -38,7 +41,9 @@ public void login(String username, String password) throws InvalidKeyException, } this.tokenFetcher.setAuthParameters(username, password); - this.setToken(this.tokenFetcher.fetchToken()); + Token token = this.tokenFetcher.fetchToken(); + this.setToken(token.getIdToken()); + this.refreshTokenStorage.setRefreshToken(token.getRefreshToken()); } public void setToken(String token) { diff --git a/src/main/java/city/makeour/moc/RefreshTokenStorage.java b/src/main/java/city/makeour/moc/RefreshTokenStorage.java new file mode 100644 index 0000000..dddedc5 --- /dev/null +++ b/src/main/java/city/makeour/moc/RefreshTokenStorage.java @@ -0,0 +1,22 @@ +package city.makeour.moc; + +public class RefreshTokenStorage implements RefreshTokenStorageInterface { + + private String refreshToken; + + public RefreshTokenStorage() { + this.refreshToken = null; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public boolean hasRefreshToken() { + return this.refreshToken != null; + } +} diff --git a/src/main/java/city/makeour/moc/RefreshTokenStorageInterface.java b/src/main/java/city/makeour/moc/RefreshTokenStorageInterface.java new file mode 100644 index 0000000..a73892c --- /dev/null +++ b/src/main/java/city/makeour/moc/RefreshTokenStorageInterface.java @@ -0,0 +1,10 @@ +package city.makeour.moc; + +public interface RefreshTokenStorageInterface { + + public String getRefreshToken(); + + public void setRefreshToken(String refreshToken); + + public boolean hasRefreshToken(); +} diff --git a/src/main/java/city/makeour/moc/Token.java b/src/main/java/city/makeour/moc/Token.java new file mode 100644 index 0000000..984834d --- /dev/null +++ b/src/main/java/city/makeour/moc/Token.java @@ -0,0 +1,19 @@ +package city.makeour.moc; + +public class Token { + private String idToken; + private String refreshToken; + + public Token(String idToken, String refreshToken) { + this.idToken = idToken; + this.refreshToken = refreshToken; + } + + public String getIdToken() { + return idToken; + } + + public String getRefreshToken() { + return refreshToken; + } +} diff --git a/src/main/java/city/makeour/moc/TokenFetcherInterface.java b/src/main/java/city/makeour/moc/TokenFetcherInterface.java index 48b13f2..fc4c7af 100644 --- a/src/main/java/city/makeour/moc/TokenFetcherInterface.java +++ b/src/main/java/city/makeour/moc/TokenFetcherInterface.java @@ -6,5 +6,5 @@ public interface TokenFetcherInterface { void setAuthParameters(String username, String password); - String fetchToken() throws InvalidKeyException, NoSuchAlgorithmException; + Token fetchToken() throws InvalidKeyException, NoSuchAlgorithmException; } diff --git a/src/main/java/city/makeour/moc/auth/srp/SrpAuthenticationHelper.java b/src/main/java/city/makeour/moc/auth/srp/SrpAuthenticationHelper.java index d2cae9e..8826bff 100644 --- a/src/main/java/city/makeour/moc/auth/srp/SrpAuthenticationHelper.java +++ b/src/main/java/city/makeour/moc/auth/srp/SrpAuthenticationHelper.java @@ -19,7 +19,6 @@ public class SrpAuthenticationHelper { private final LargeN N; private final SmallG g = new SmallG(); private final SmallK k; - private final String userPoolId; private final String userPoolName;; public SrpAuthenticationHelper(String userPoolId) { @@ -27,7 +26,6 @@ public SrpAuthenticationHelper(String userPoolId) { this.a = new SmallA(N); this.A = new LargeA(N, this.a, this.g); this.k = new SmallK(N, g); - this.userPoolId = userPoolId; this.userPoolName = userPoolId.split("_")[1]; } @@ -81,12 +79,6 @@ public String calculateSignature(String userIdForSRP, String secretBlock, String pos += secretBlockBytes.length; System.arraycopy(timestampBytes, 0, message, pos, timestampBytes.length); - String p = new String(poolNameBytes); - String u = new String(userIdBytes); - String s = new String(secretBlockBytes); - String t = new String(timestampBytes); - String hm = Helper.toHex(message); - byte[] rawSignature = mac.doFinal(message); return Base64.getEncoder().encodeToString(rawSignature); } diff --git a/src/test/java/city/makeour/moc/CognitoAuthenticationHelperTest.java b/src/test/java/city/makeour/moc/CognitoAuthenticationHelperTest.java deleted file mode 100644 index 1942321..0000000 --- a/src/test/java/city/makeour/moc/CognitoAuthenticationHelperTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package city.makeour.moc; - -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Description; - -import software.amazon.awssdk.regions.Region; - -public class CognitoAuthenticationHelperTest { - - // Test cases for the CognitoAuthenticationHelper class - // Add your test methods here - - static { - System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog"); - System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true"); - System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http", "DEBUG"); - System.setProperty("software.amazon.awssdk.eventstream.rpc", "debug"); - } - - // Example test method - @Test - @Description("CognitoAuthenticatorのloginメソッドのテスト") - public void testLogin() { - String userPoolId = "ap-northeast-1_nXSBLO7v6"; - String clientId = "3d7d0piq75halieshbi7o8keca"; - String username = "ushio.s@gmail.com"; - String password = ""; - - CognitoAuthenticator authenticator = new CognitoAuthenticator( - Region.AP_NORTHEAST_1, - clientId, - userPoolId); - - String idToken = authenticator.login(username, password); - - System.out.println("ID Token:"); - System.out.println(idToken); - } -} diff --git a/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java b/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java index 1a60b9c..b24fc0f 100644 --- a/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java +++ b/src/test/java/city/makeour/moc/FetchCognitoTokenTest.java @@ -3,19 +3,26 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; class FetchCognitoTokenTest { + // TODO: テストのスキップについてきちんと動作確認をする。 @Test + @EnabledIfEnvironmentVariable(named = "TEST_COGNITO_USER_POOL_ID", matches = ".+") + @EnabledIfEnvironmentVariable(named = "TEST_COGNITO_CLIENT_ID", matches = ".+") + @EnabledIfEnvironmentVariable(named = "TEST_COGNITO_USERNAME", matches = ".+") + @EnabledIfEnvironmentVariable(named = "TEST_COGNITO_PASSWORD", matches = ".+") public void testFetchTokenWithSrpAuth() throws Exception { - String cognitoUserPoolId = "ap-northeast-1_nXSBLO7v6"; - String cognitoClientId = "3d7d0piq75halieshbi7o8keca"; - String username = "ushio.s@gmail.com"; - String password = ""; + String cognitoUserPoolId = System.getenv("TEST_COGNITO_USER_POOL_ID"); + String cognitoClientId = System.getenv("TEST_COGNITO_CLIENT_ID"); + String username = System.getenv("TEST_COGNITO_USERNAME"); + String password = System.getenv("TEST_COGNITO_PASSWORD"); FetchCognitoToken fetchCognitoToken = new FetchCognitoToken(cognitoUserPoolId, cognitoClientId); fetchCognitoToken.setAuthParameters(username, password); - String token = fetchCognitoToken.fetchTokenWithSrpAuth(); - assertNotNull(token); + Token token = fetchCognitoToken.fetchTokenWithSrpAuth(); + assertNotNull(token.getIdToken()); + assertNotNull(token.getRefreshToken()); } } \ No newline at end of file From bbe3fed3aa73e0154951748b36b79d391c349db6 Mon Sep 17 00:00:00 2001 From: Shugo USHIO Date: Sat, 3 May 2025 00:43:36 +0900 Subject: [PATCH 36/36] Update src/main/java/city/makeour/moc/auth/srp/SmallG.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/java/city/makeour/moc/auth/srp/SmallG.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/city/makeour/moc/auth/srp/SmallG.java b/src/main/java/city/makeour/moc/auth/srp/SmallG.java index 339463a..9debe97 100644 --- a/src/main/java/city/makeour/moc/auth/srp/SmallG.java +++ b/src/main/java/city/makeour/moc/auth/srp/SmallG.java @@ -3,7 +3,7 @@ import java.math.BigInteger; public class SmallG { - private static final BigInteger G = BigInteger.valueOf(2);; + private static final BigInteger G = BigInteger.valueOf(2); public BigInteger value() { return G;