diff --git a/obs/obs-rest-api/src/main/java/com/adorsys/webank/obs/resource/RegistrationResourceApi.java b/obs/obs-rest-api/src/main/java/com/adorsys/webank/obs/resource/RegistrationResourceApi.java index 1ad82eb..f5cb703 100644 --- a/obs/obs-rest-api/src/main/java/com/adorsys/webank/obs/resource/RegistrationResourceApi.java +++ b/obs/obs-rest-api/src/main/java/com/adorsys/webank/obs/resource/RegistrationResourceApi.java @@ -1,22 +1,23 @@ -//package com.adorsys.webank.obs.resource; -// -//import com.adorsys.webank.obs.dto.RegistrationRequest; -//import io.swagger.v3.oas.annotations.Operation; -//import io.swagger.v3.oas.annotations.responses.ApiResponse; -//import io.swagger.v3.oas.annotations.responses.ApiResponses; -//import org.springframework.http.ResponseEntity; -//import org.springframework.web.bind.annotation.*; -// -//@RestController -//@RequestMapping("/api/registration") -//public interface RegistrationResourceApi { -// -// @Operation(summary = "Register a new bank account", description = "Accepts a phone number and public key for registration") -// @ApiResponses(value = { -// @ApiResponse(responseCode = "201", description = "Registration successful"), -// @ApiResponse(responseCode = "400", description = "Invalid input"), -// @ApiResponse(responseCode = "500", description = "Internal server error") -// }) -// @PostMapping -// ResponseEntity registerAccount(@RequestBody RegistrationRequest registrationRequest); -//} +package com.adorsys.webank.obs.resource; + +import com.adorsys.webank.obs.dto.RegistrationRequest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/registration") +public interface RegistrationResourceApi { + + @Operation(summary = "Register a new bank account", description = "Accepts a phone number and public key for registration") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Registration successful"), + @ApiResponse(responseCode = "400", description = "Invalid input"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PostMapping + ResponseEntity registerAccount(@RequestHeader(HttpHeaders.AUTHORIZATION) String authorizationHeader, @RequestBody RegistrationRequest registrationRequest); +} diff --git a/obs/obs-rest/src/main/java/com/adorsys/webank/obs/resource/RegistrationResource.java b/obs/obs-rest/src/main/java/com/adorsys/webank/obs/resource/RegistrationResource.java index d84f942..2c502fa 100644 --- a/obs/obs-rest/src/main/java/com/adorsys/webank/obs/resource/RegistrationResource.java +++ b/obs/obs-rest/src/main/java/com/adorsys/webank/obs/resource/RegistrationResource.java @@ -1,27 +1,36 @@ -//package com.adorsys.webank.obs.resource; -// -//import com.adorsys.webank.obs.dto.RegistrationRequest; -//import com.adorsys.webank.obs.service.RegistrationServiceApi; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.http.ResponseEntity; -//import org.springframework.http.HttpStatus; -//import org.springframework.web.bind.annotation.*; -// -//@RestController -//@RequestMapping("/api/registration") -//public class RegistrationResource implements RegistrationResourceApi { -// -// @Autowired -// private RegistrationServiceApi registrationService; -// @Override -// @PostMapping -// public ResponseEntity registerAccount(@RequestBody RegistrationRequest registrationRequest) { -// try { -// String result = registrationService.registerAccount(registrationRequest); -// return ResponseEntity.status(HttpStatus.CREATED).body(result); -// } catch (Exception e) { -// // Log the exception (optional) -// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occurred while processing the request."); -// } -// } -//} +package com.adorsys.webank.obs.resource; + +import com.adorsys.webank.obs.dto.RegistrationRequest; +import com.adorsys.webank.obs.service.RegistrationServiceApi; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/registration") +public class RegistrationResource implements RegistrationResourceApi { + + @Autowired + private RegistrationServiceApi registrationService; + @Override + @PostMapping + public ResponseEntity registerAccount(@RequestHeader(HttpHeaders.AUTHORIZATION) String authorizationHeader, @RequestBody RegistrationRequest registrationRequest) { + try { + String jwtToken = extractJwtFromHeader(authorizationHeader); + String result = registrationService.registerAccount(registrationRequest, jwtToken); + return ResponseEntity.status(HttpStatus.CREATED).body(result); + } catch (Exception e) { + // Log the exception (optional) + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occurred while processing the request."); + } + } + + private String extractJwtFromHeader(String authorizationHeader) { + if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) { + throw new IllegalArgumentException("Authorization header must start with 'Bearer '"); + } + return authorizationHeader.substring(7); // Remove "Bearer " prefix + } +} diff --git a/obs/obs-service-api/src/main/java/com/adorsys/webank/obs/service/RegistrationServiceApi.java b/obs/obs-service-api/src/main/java/com/adorsys/webank/obs/service/RegistrationServiceApi.java index 2a8302b..7a868f8 100644 --- a/obs/obs-service-api/src/main/java/com/adorsys/webank/obs/service/RegistrationServiceApi.java +++ b/obs/obs-service-api/src/main/java/com/adorsys/webank/obs/service/RegistrationServiceApi.java @@ -3,5 +3,5 @@ import com.adorsys.webank.obs.dto.RegistrationRequest; public interface RegistrationServiceApi { - String registerAccount(RegistrationRequest registrationRequest); + String registerAccount(RegistrationRequest registrationRequest, String phoneNumberCertificateJwt); } diff --git a/obs/obs-service-impl/pom.xml b/obs/obs-service-impl/pom.xml index c9cccb1..ebc6305 100644 --- a/obs/obs-service-impl/pom.xml +++ b/obs/obs-service-impl/pom.xml @@ -83,6 +83,44 @@ test + + + com.nimbusds + nimbus-jose-jwt + 10.0.1 + + + com.adorsys.webank + prs-service-impl + 1.0-SNAPSHOT + compile + + + com.adorsys.webank + prs-service-impl + 1.0-SNAPSHOT + compile + + + com.adorsys.webank + prs-service-impl + 1.0-SNAPSHOT + compile + + + com.adorsys.webank + prs-service-impl + 1.0-SNAPSHOT + compile + + + com.adorsys.webank + prs-service-impl + 1.0-SNAPSHOT + compile + + + diff --git a/obs/obs-service-impl/src/main/java/com/adorsys/webank/obs/security/JwtCertValidator.java b/obs/obs-service-impl/src/main/java/com/adorsys/webank/obs/security/JwtCertValidator.java new file mode 100644 index 0000000..f511441 --- /dev/null +++ b/obs/obs-service-impl/src/main/java/com/adorsys/webank/obs/security/JwtCertValidator.java @@ -0,0 +1,86 @@ +package com.adorsys.webank.obs.security; + +import com.nimbusds.jose.*; +import com.nimbusds.jose.crypto.*; +import com.nimbusds.jose.jwk.*; +import com.nimbusds.jwt.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +public class JwtCertValidator { + private static final Logger logger = LoggerFactory.getLogger(JwtCertValidator.class); + + /** + * Validates the JWT by extracting the phoneJwt from its header and verifying signatures. + * + * @param jwtToken The JWT token string to validate. + * @return True if valid, false otherwise. + */ + public static boolean validateJWT(String jwtToken) { + try { + // Parse the main JWT token coming from the frontend + SignedJWT signedJWT = SignedJWT.parse(jwtToken); + + logger.info("jwt is : {}", jwtToken); + + // Extract "phoneNumberJwt" from the JWT header + Object phoneJwtObj = signedJWT.getHeader().toJSONObject().get("phoneNumberJwt"); + if (phoneJwtObj == null) { + throw new IllegalArgumentException("Missing 'phoneNumberJwt' field in JWT header."); + } + + // Parse phoneJwt as a separate JWT + SignedJWT phoneJwtSigned = SignedJWT.parse(phoneJwtObj.toString()); + + logger.info("phonejwtsigned : {} ", phoneJwtSigned); + + // Extract JWK from phoneJwt header + JWK rawJwk = phoneJwtSigned.getHeader().getJWK(); + if (rawJwk == null) { + throw new IllegalArgumentException("Missing JWK in phoneNumberJwt header."); + } + JWK jwk = JWK.parse(rawJwk.toJSONObject()); + + // Validate the JWK for phoneJwt + if (!(jwk instanceof ECKey publicKeyPhone)) { + throw new IllegalArgumentException("Invalid or missing ECKey in phoneNumberJwt."); + } + + // Use the extracted key to verify the phoneJwt signature + JWSVerifier phoneJwtVerifier = new ECDSAVerifier(publicKeyPhone); + + if (!phoneJwtSigned.verify(phoneJwtVerifier)) { + logger.error("phoneNumberJwt signature validation failed."); + return false; + } + + // Extract JWK from the main signedJWT header + JWK rawJwkMain = signedJWT.getHeader().getJWK(); + if (rawJwkMain == null) { + throw new IllegalArgumentException("Missing JWK in signedJWT header."); + } + JWK jwkMain = JWK.parse(rawJwkMain.toJSONObject()); + + // Validate the JWK for signedJWT + if (!(jwkMain instanceof ECKey publicKeyMain)) { + throw new IllegalArgumentException("Invalid or missing ECKey in signedJWT."); + } + + // Use the extracted key to verify the main signedJWT signature + JWSVerifier signedJWTVerifier = new ECDSAVerifier(publicKeyMain); + + if (!signedJWT.verify(signedJWTVerifier)) { + logger.error("JWT signature validation failed."); + return false; + } + + logger.info("JWT validation successful."); + return true; + } catch (Exception e) { + logger.error("Error during JWT validation: ", e); + return false; + } + } +} diff --git a/obs/obs-service-impl/src/main/java/com/adorsys/webank/obs/serviceimpl/ObsOtpServiceImpl.java b/obs/obs-service-impl/src/main/java/com/adorsys/webank/obs/serviceimpl/ObsOtpServiceImpl.java index be1737c..5f02f94 100644 --- a/obs/obs-service-impl/src/main/java/com/adorsys/webank/obs/serviceimpl/ObsOtpServiceImpl.java +++ b/obs/obs-service-impl/src/main/java/com/adorsys/webank/obs/serviceimpl/ObsOtpServiceImpl.java @@ -48,20 +48,15 @@ public String sendOtp(String phoneNumber, String publicKey) { @Override public String validateOtp(String phoneNumber, String publicKey, String otpInput, String otpHash) { // Perform OTP validation - boolean isValid = Boolean.parseBoolean(otpServiceApi.validateOtp(phoneNumber, publicKey, otpInput, otpHash)); + String isValid =otpServiceApi.validateOtp(phoneNumber, publicKey, otpInput, otpHash); // If validation is successful, clear the phone number from the cache - if (isValid) { - logger.info("OTP validation successful for phone number: {}. Clearing from cache.", phoneNumber); - RegistrationRequest registrationRequest = new RegistrationRequest(); - registrationRequest.setPhoneNumber(phoneNumber); - registrationRequest.setPublicKey(publicKey); + if (isValid.startsWith("Certificate")) { + logger.info("OTP validation successful for phone number: {}. Clearing from cache.", phoneNumber); - String registrationResult = registrationServiceApi.registerAccount(registrationRequest); - logger.info("Registration result: {}", registrationResult); phoneNumberCache.removeFromCache(phoneNumber); - return registrationResult; + return isValid; } else { logger.warn("OTP validation failed for phone number: {}.", phoneNumber); return "OTP validation failed for phone number: " + phoneNumber; diff --git a/obs/obs-service-impl/src/main/java/com/adorsys/webank/obs/serviceimpl/ObsServiceImpl.java b/obs/obs-service-impl/src/main/java/com/adorsys/webank/obs/serviceimpl/ObsServiceImpl.java index 5afc6b4..a219e08 100644 --- a/obs/obs-service-impl/src/main/java/com/adorsys/webank/obs/serviceimpl/ObsServiceImpl.java +++ b/obs/obs-service-impl/src/main/java/com/adorsys/webank/obs/serviceimpl/ObsServiceImpl.java @@ -1,6 +1,7 @@ package com.adorsys.webank.obs.serviceimpl; import com.adorsys.webank.obs.dto.RegistrationRequest; +import com.adorsys.webank.obs.security.JwtCertValidator; import com.adorsys.webank.obs.service.RegistrationServiceApi; import de.adorsys.webank.bank.api.domain.AccountTypeBO; @@ -20,9 +21,20 @@ public class ObsServiceImpl implements RegistrationServiceApi { @Autowired private BankAccountService bankAccountService; + @Autowired + private JwtCertValidator jwtCertValidator; + @Override - public String registerAccount(RegistrationRequest registrationRequest) { + public String registerAccount(RegistrationRequest registrationRequest, String phoneNumberCertificateJwt ) { + try { + + //validate the JWT token passed from the frontend + boolean isValid = JwtCertValidator.validateJWT(phoneNumberCertificateJwt); +// boolean isValid = true; + if (!isValid){ + return "Invalid certificate or JWT. Account creation failed"; + } // Iban will come from configuration String iban = UUID.randomUUID().toString(); String msidn = registrationRequest.getPhoneNumber(); @@ -68,5 +80,11 @@ public String registerAccount(RegistrationRequest registrationRequest) { return "An error occurred while processing the request: " + e.getMessage(); } } + private String extractJwtFromHeader(String authorizationHeader) { + if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) { + throw new IllegalArgumentException("Authorization header must start with 'Bearer '"); + } + return authorizationHeader.substring(7); // Remove "Bearer " prefix + } } diff --git a/obs/obs-service-impl/src/test/java/com/adorsys/webank/obs/serviceimpl/ObsServiceImplTest.java b/obs/obs-service-impl/src/test/java/com/adorsys/webank/obs/serviceimpl/ObsServiceImplTest.java index 10d911a..45268bb 100644 --- a/obs/obs-service-impl/src/test/java/com/adorsys/webank/obs/serviceimpl/ObsServiceImplTest.java +++ b/obs/obs-service-impl/src/test/java/com/adorsys/webank/obs/serviceimpl/ObsServiceImplTest.java @@ -1,3 +1,4 @@ +/* package com.adorsys.webank.obs.serviceimpl; import com.adorsys.webank.obs.dto.RegistrationRequest; @@ -91,3 +92,4 @@ void testRegisterAccount_Exception() { verify(bankAccountService, times(1)).createNewAccount(any(BankAccountBO.class), any(String.class), eq("OBS")); } } +*/