50 Commits

Author SHA1 Message Date
Kshitij e1da884fbb Bumped backend version to 0.0.3. Tagging now. 2025-08-03 20:57:04 +05:30
Kshitij 490578cfe2 Added readme file. 2025-08-03 20:52:58 +05:30
Kshitij c45bc27c81 Fixed version for backend. It's not 1.5, it's still on 0.0.2. Dockerfile is at 1.5. 2025-08-03 20:42:39 +05:30
Kshitij 95d77fb3fe Added application.properties.bak to gitignore. 2025-08-03 20:35:54 +05:30
Kshitij 92b335410b Now passing in application.properties instead of hard coded values. 2025-08-03 20:35:31 +05:30
Kshitij b7ce85a5ec Lotta changes to Dockerfile.
- Bumped version to 1.5.
- Copying only the jar file now.
- No longer creating temp directory for downloading files. Fixed that in this version 0.0.2 of backend.
- Changed port to 8080.
- Updated CMD accn to new jar file.
2025-08-03 20:32:39 +05:30
Kshitij 7ae2eca31b Added instructions to create .p12 file using keytool (part of JDK) 2025-08-03 20:28:13 +05:30
Kshitij 0aba0e7911 Removed keystore.p12 2025-08-03 20:25:51 +05:30
Kshitij 7411f8b4fa Bumped version to 1.5 in pom.xml 2025-08-03 20:13:07 +05:30
SonaliChaudhari b2147537ca Handled Download API by not passing sensitive info 2025-07-27 15:05:33 +05:30
SonaliChaudhari 063bfa794a Implemented Cache for decrypted private key and handled refresh token 2025-07-25 13:36:15 +05:30
Kshitij 2622667de4 Moved contents from ./Backend/src/ to ./src/ 2025-07-23 14:54:13 +05:30
SonaliChaudhari dd958b0fde REMOVED OLD ENDPOINTS AND SOME ENCRYPTION AND DECRYPTION METHODS 2025-07-23 11:51:01 +05:30
Kshitij 4e028dd971 Added wiki directory in gitignore. 2025-07-04 01:45:15 +05:30
Kshitij a6325d5681 Set column type to longblob for encrypted_key field in entity/FileMetadata 2025-07-03 17:06:12 +05:30
Kshitij c5ff741f8c Refactor JWT config and enhance security, improve file download, and fix refresh token cleanup
- Restricted public auth endpoints to only /login and /register in SecurityConfig
- Added contentLength header and improved error response in FileController download API
- Refactored JwtService to load secret key and expiration from application properties
- Improved signing key handling using Base64 decoding
- Updated RefreshTokenRepository with @Transactional @Modifying delete query
- Ensured proper refresh token cleanup with flush() in RefreshTokenService
- Annotated refresh token methods with @Transactional for consistency
2025-07-03 16:59:29 +05:30
Kshitij 3920ec7fbd Using base64 encoded JWT secret key 2025-07-03 16:36:33 +05:30
Kshitij 4af5aabd42 Refactor encryption system to support hybrid RSA-AES encryption per file
- Changed file upload logic to:
  - Generate random AES key per file
  - Encrypt AES key using user's RSA public key
  - Store encrypted AES key, IV, and salt in FileMetadata entity

- Changed file download logic to:
  - Decrypt AES key using user's RSA private key (encrypted with password-derived AES)
  - Use decrypted AES key and IV to decrypt file contents from HDFS

- Modified FileMetadata entity:
  - Changed `encryptedKey` to @Lob byte[] to support large encrypted AES keys

- Updated User entity:
  - Encrypted private RSA key with password-derived AES
  - Stored associated salt and IV for decryption

- Updated AuthenticationService:
  - Generate RSA keypair during sign-up
  - Encrypt and store private key with AES (salt, IV)
  - Create user folder in HDFS upon registration

- Updated FileService:
  - Rewrote upload and download logic to support hybrid encryption
  - Handled key wrapping and unwrapping securely
  - Added logging for upload/download events

- Fixed FileController upload to remove password from endpoint
  - Password now only required during download for private key decryption

- Updated EncryptionUtil and RSAKeyUtil:
  - Added RSA OAEP support and helper methods
  - Added AES key generation, encryption, decryption utilities

FILE UPLOAD AND ENCRYPTION WORKS! TESTED USING HEXDUMP.
2025-07-03 16:22:41 +05:30
Kshitij 23eda639c0 Refactor file upload/download with service layer and secure encryption
- Replaced direct encryption logic in FileController with FileService delegation
- Added JWT-based username extraction in file operations
- Updated FileMetadata entity to include `uploadedAt` field and removed redundant getters/setters
- Refactored EncryptionUtil:
  - Switched to AES-CBC with PBKDF2 key derivation
  - Removed RSA-based encryption logic
  - Added salt and IV generation helpers
- Changed JwtAuthenticationFilter to fetch user by username (not email)
- Renamed method in FileMetadataRepository to match new parameter order

FILE UPLOAD NOW WORKS! TESTED USING CURL.
2025-07-03 15:20:10 +05:30
Kshitij f06dbd84ad Add user registration endpoint and HDFS directory creation
- Implemented a new registration endpoint in AuthController to handle user sign-ups.
- Integrated AuthenticationService to save the user and create a corresponding HDFS directory for the user upon registration.
- Updated User entity to return the username instead of email in getUsername method.
2025-07-03 14:24:04 +05:30
Kshitij 0661b2540f Added execution permission for mvnw 2025-07-03 04:49:41 +05:30
Kshitij 222fd796f2 Update application.properties for production deployment
- Set static MySQL connection with IP and password
- Changed server port from 8081 to 8080
- Enabled HTTPS with keystore configuration
- Exposed actuator endpoints for monitoring
- Removed old and unused commented-out configurations
2025-07-03 04:49:16 +05:30
Kshitij 7f6b2eb344 Refactor and secure backend configuration, DTOs, and authentication flow
- Updated pom.xml: removed redundant tags, grouped dependencies, added scopes, and upgraded plugins
- Enhanced RegisterUserDto with validation annotations and added missing fields (username, fullname)
- Updated User entity with builder constructor and removed redundant getters/setters
- Completed FileMetadata entity with Lombok and required setters/getters
- Improved HDFSConfig with correct annotation and clearer exception message
- Adjusted HTTP to HTTPS redirect port (8085 -> 8443)
- Allowed /actuator/** in SecurityConfig and disabled deprecated XSS protection
- Skipped JWT filter for /api/auth and /actuator paths
- Refactored AuthenticationService to use builder pattern and RSA key injection
- Fixed application.properties for static MySQL connection (removed ${MYSQL_PASSWORD})
2025-07-03 04:48:29 +05:30
Kshitij 88fd49c807 Refactor User entity and SignupRequest DTO to resolve method access issues and improve compatibility
- Added explicit getter/setter methods in User entity for use with service layer
- Implemented standard getters/setters in SignupRequest DTO
- Replaced incorrect imports from models.User to entity.User across services and repository
2025-07-03 03:54:20 +05:30
Kshitij 12355f25c7 Refactor Auth and HDFS controllers, fix User model, and improve HDFS config
- Rewrote AuthController to inject all dependencies via constructor
- Fixed token refresh/login logic and added rate limiter and blacklist support
- Implemented getters in LoginRequest DTO
- Updated User model to implement UserDetails and extend entity.User
- Switched HDFScontroller to use entity.User instead of models.User
- Rewrote HDFSConfig to include static getHDFS() method and secure config via env vars
- Simplified JwtService, added overload for entity.User, and fixed key handling
2025-07-03 03:47:08 +05:30
Kshitij 9cb9c67b09 Revoke refresh token on logout for enhanced session security 2025-07-03 03:21:53 +05:30
Kshitij 31f13b980b Update login response to return both access and refresh tokens 2025-07-03 03:19:15 +05:30
Kshitij 2379d95759 Add refresh token support with /api/auth/refresh endpoint
- RefreshToken entity added with 1-token-per-user logic.
- JWT can be renewed without full login using refresh token.
2025-07-03 03:15:31 +05:30
Kshitij 178a32f908 Removed obsolete files. Refactored certain files to use newer ones. 2025-07-03 03:10:51 +05:30
Kshitij 218ccb720f Implement token blacklist for JWT logout support
- TokenBlacklistService tracks invalidated tokens using Caffeine cache.
- AuthController adds tokens to blacklist on logout.
- JwtAuthenticationFilter blocks blacklisted tokens during authentication.
2025-07-03 02:57:29 +05:30
Kshitij dd52421392 Add brute-force protection with rate limiting on login
- Caffeine cache used to allow max 5 login attempts per minute.
- Login endpoint blocks IPs exceeding rate, returns 429 status.
- Failed attempts are reset after successful login or after 1 minute.
2025-07-03 02:47:19 +05:30
Kshitij aaf5d2dbd8 Add JWT authentication filter to secure protected routes
- Intercepts all requests and checks for Bearer token.
- Validates token signature and expiry using JwtService.
- Loads user from DB and sets authentication context.
- Sends 401 Unauthorized if token is missing, invalid, or expired.
2025-07-03 02:43:56 +05:30
Kshitij 4b21828510 Add JWT-based login and logout endpoints
- POST /api/auth/login authenticates user and returns JWT token.
- POST /api/auth/logout is a placeholder (client deletes token).
- JwtService handles token creation and expiry validation.
2025-07-03 02:38:55 +05:30
Kshitij e14f27830e Add HTTP to HTTPS redirect configuration using embedded Tomcat
- HTTP connector on port 8080 redirects to HTTPS (8443).
- Ensures users accessing http:// are forwarded to https://
- No need for external proxy (e.g., Nginx) for redirection.
2025-07-03 02:37:13 +05:30
Kshitij 04f291910f Harden Spring Security configuration and enforce HTTPS
- All requests now require HTTPS.
- Stateless sessions enabled for JWT-based auth.
- XSS, HSTS, and Frame-Options headers added.
- /api/auth/** is public, all other routes require authentication.
- CSRF disabled (assumes token-based auth).
2025-07-03 02:35:25 +05:30
Kshitij c88cb5ac0e Add secure file upload and download with per-user AES encryption
- FileController encrypts uploads using AES-GCM with salt and IV.
- Downloads are decrypted on-the-fly using user-supplied password.
- File metadata (salt, IV, username, path) stored in DB.
2025-07-03 02:32:42 +05:30
Kshitij c133617990 Move UserService to correct 'services' package and update related imports 2025-07-03 02:30:20 +05:30
Kshitij a9e7d23c3c Add HTTPS configuration for production profile
- SSL enabled on port 8443 using self-signed keystore.
- application-prod.yml configured for TLS.
2025-07-03 02:26:56 +05:30
Kshitij 91e0d50c0a Implement AES-GCM file encryption per user
- EncryptionService uses PBKDF2 to derive a key from user password and salt.
- AES-GCM encryption with 128-bit tag and 12-byte IV.
- Ready for streaming encryption to/from HDFS without temp files.
2025-07-03 02:26:46 +05:30
Kshitij 39aa31625d Add username validation and password breach check to signup
- Enforced alphanumeric-only usernames using regex validation.
- Passwords must be >= 8 chars and checked against haveibeenpwned.com.
- Improved SignupRequest DTO with validation annotations.
- Implemented UserService to handle password validation and encoding.
2025-07-03 02:26:30 +05:30
Kshitij 8ae2ced645 Harden ApplicationConfiguration with stronger BCrypt, cleanup, and security improvements
- Increased BCrypt password encoder strength to 12 for better hashing security.
- Switched to PasswordEncoder interface for flexibility (e.g., Argon2 support).
- Removed unused import (java.security.AuthProvider).
- Made all @Bean methods explicitly public.
- Added JavaDoc comments for better readability and maintainability.
- Improved exception message in UserDetailsService for clarity.
2025-07-03 02:01:05 +05:30
Kshitij d3e1aff0fb Fix: Downloading issue when deployed. Added a temp /Skycrate/downloaded directory and changed ownership to 1000:1000, this allows the file to be temporarily fetched to the server, then downloaded on client's site. Hoping to find a better (feasable) solution to this later. 2025-04-21 10:32:22 +05:30
Kshitij 18876f2780 Changed hdfs IP to docker container's hostname. 2025-04-20 20:51:01 +05:30
Kshitij b41b1071e6 Changed port from 8080 to 8081 in application.properties and exposed 8081 in Dockerfile for ref. 2025-04-20 04:25:10 +05:30
Kshitij c83898e34c Forgot to change the target dir in Dockerfile, my bad 😅 2025-04-20 03:57:18 +05:30
Kshitij 769ac030b3 Updated username, host and changed password to env var for DB in application.properties. 2025-04-20 03:54:04 +05:30
Kshitij 3801a9b5c2 Added maven binary directory in gitignore. 2025-04-20 03:50:13 +05:30
Kshitij 152ada0bad Added Dockerfile to build Docker image for backend. 2025-04-20 03:39:49 +05:30
vedang29 3b5736dc8e Retrieve Username by Email 2025-04-18 16:54:42 +05:30
SonaliChaudhari 03bdb5d898 Implemented Encryption and Decryption Needed to be as saved Response 2025-04-18 00:28:45 +05:30
49 changed files with 1982 additions and 1125 deletions
+5
View File
@@ -1,3 +1,5 @@
src/main/resources/application.properties.bak
wiki/
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
@@ -31,3 +33,6 @@ build/
### VS Code ###
.vscode/
### apach-maven binary ###
apache-maven-3.9.6
+31
View File
@@ -0,0 +1,31 @@
## BACKEND ##
# Base image
FROM debian:12-slim
# Metadata
LABEL maintainer="kshitijka"
LABEL version=1.5
LABEL description="Skycrate is a web based file management system that uses Hadoop as filesystem."
# Update & upgrade & install & rm
RUN apt-get update && apt-get upgrade -y && \
apt-get install -y openjdk-17-jdk && \
rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN useradd -s /bin/bash skycrateBack
# Create work dir
RUN mkdir /app
RUN chown -R skycrateBack:skycrateBack /app
COPY ./target/skycrateBackend-0.0.3.jar /app
WORKDIR /app
# Switch user
USER skycrateBack
# Expose port for backend
EXPOSE 8080
CMD ["java", "-jar", "skycrateBackend-0.0.3.jar"]
+7
View File
@@ -0,0 +1,7 @@
# Skycrate-Backend
---
This repository holds code for [Skycrate](https://git.kska.io/notkshitij/Skycrate) backend.
---
Vendored Regular → Executable
View File
+148 -164
View File
@@ -1,180 +1,164 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.skycrate.backend</groupId>
<artifactId>skycrateBackend</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>skycrateBackend</name>
<description>Cloud Storage App using HDFS</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<relativePath/>
</parent>
<groupId>com.skycrate.backend</groupId>
<artifactId>skycrateBackend</artifactId>
<version>0.0.3</version>
<name>skycrateBackend</name>
<description>Cloud Storage App using HDFS</description>
<!-- NEWLY ADDED DEPENDECIES-->
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.4</version>
</dependency>
<properties>
<java.version>17</java.version>
</properties>
<!-- Spring Boot Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.2.4</version>
</dependency>
<dependencies>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Spring Boot Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>3.2.4</version>
</dependency>
<!-- MySQL JDBC -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- Spring Boot Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>3.2.4</version>
</dependency>
<!-- Hadoop HDFS -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>3.4.1</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.4.1</version>
</dependency>
<!-- JSON Web Token (JWT) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
<!-- Commons IO -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<!-- Hadoop Dependencies -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>3.4.1</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Caffeine Cache -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>3.4.1</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.4.1</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Logging (SLF4J + Logback) -->
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-api</artifactId>-->
<!-- <version>1.7.36</version>-->
<!-- </dependency>-->
<!-- Caching -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
</dependencies>
<!-- <dependency>-->
<!-- <groupId>ch.qos.logback</groupId>-->
<!-- <artifactId>logback-classic</artifactId>-->
<!-- <version>1.2.11</version>-->
<!-- </dependency>-->
<!-- Apache Commons IO (for file operations) -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<build>
<plugins>
<!-- Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- Spring Boot Plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -1,7 +1,5 @@
package com.skycrate.backend.skycrateBackend.config;
import java.security.AuthProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
@@ -10,38 +8,48 @@ import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
// Application-wide security configuration.
// Configures user authentication, password encoding, and authentication provider.
@Configuration
public class ApplicationConfiguration {
private final UserRepository userRepository;
public ApplicationConfiguration(UserRepository userRepository){
this.userRepository=userRepository;
public ApplicationConfiguration(UserRepository userRepository) {
this.userRepository = userRepository;
}
// Custom UserDetailsService to fetch user details by email.
@Bean
UserDetailsService userDetailsService() {
public UserDetailsService userDetailsService() {
return username -> userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
}
@Bean
BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
.orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + username));
}
// BCrypt password encoder with a higher strength for better security.
// Cost factor 12 is considered a good balance for production use.
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception{
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
// AuthenticationProvider using DAO with custom user service and password encoder.
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService());
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
// Provides the AuthenticationManager for authenticating credentials.
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
AuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider authprovider=new DaoAuthenticationProvider();
authprovider.setUserDetailsService(userDetailsService());
authprovider.setPasswordEncoder(passwordEncoder());
return authprovider;
}
}
@@ -0,0 +1,24 @@
package com.skycrate.backend.skycrateBackend.config;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CaffeineCacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(30, TimeUnit.MINUTES) // Cache expiry time
.maximumSize(100)); // Maximum cache size
return cacheManager;
}
}
@@ -1,6 +1,6 @@
package com.skycrate.backend.skycrateBackend.config;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configuration; // Hadoop Configuration
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.security.UserGroupInformation;
import org.springframework.context.annotation.Bean;
@@ -8,13 +8,32 @@ import org.springframework.context.annotation.Bean;
import java.net.URI;
import java.security.PrivilegedExceptionAction;
@org.springframework.context.annotation.Configuration
public class HDFSConfig {
public static FileSystem getHDFS() throws Exception {
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.29.56:9000");
return FileSystem.get(new URI("hdfs://192.168.29.56:9000"), conf);
private static final String HDFS_URI = System.getenv("HDFS_URI"); // e.g., hdfs://namenode:9000
private static final String HDFS_USER = System.getenv("HDFS_USER"); // e.g., hdfsuser
@Bean
public FileSystem fileSystem() throws Exception {
return getHDFS();
}
public static FileSystem getHDFS() throws Exception {
if (HDFS_URI == null || HDFS_URI.isBlank()) {
throw new IllegalStateException("HDFS_URI environment variable not set.");
}
Configuration conf = new Configuration();
conf.set("fs.defaultFS", HDFS_URI);
if (HDFS_USER != null && !HDFS_USER.isBlank()) {
return UserGroupInformation.createRemoteUser(HDFS_USER)
.doAs((PrivilegedExceptionAction<FileSystem>) () ->
FileSystem.get(new URI(HDFS_URI), conf)
);
} else {
return FileSystem.get(new URI(HDFS_URI), conf);
}
}
}
@@ -0,0 +1,23 @@
package com.skycrate.backend.skycrateBackend.config;
import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HttpToHttpsRedirectConfig {
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> containerCustomizer() {
return factory -> {
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setScheme("http");
connector.setPort(8085); // HTTP port
connector.setSecure(false);
connector.setRedirectPort(8443); // HTTPS port
factory.addAdditionalTomcatConnectors(connector);
};
}
}
@@ -1,77 +0,0 @@
package com.skycrate.backend.skycrateBackend.config;
import java.io.IOException;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import com.skycrate.backend.skycrateBackend.services.JwtService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final HandlerExceptionResolver handlerExceptionResolver;
private JwtService jwtService;
private UserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtService jwtService,UserDetailsService userDetailsService,HandlerExceptionResolver handlerExceptionResolver){
this.handlerExceptionResolver=handlerExceptionResolver;
this.jwtService=jwtService;
this.userDetailsService=userDetailsService;
}
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
final String authHeader=request.getHeader("Authorization");
if (authHeader==null || !authHeader.startsWith("Bearer")){
filterChain.doFilter(request, response);
return;
}
try {
final String userjwt=authHeader.substring(7);
final String userEmail=jwtService.extractUsername(userjwt);
Authentication authentication=SecurityContextHolder.getContext().getAuthentication();
if(userEmail!=null && authentication==null){
UserDetails userDetails=this.userDetailsService.loadUserByUsername(userEmail);
if (jwtService.isTokenValid(userjwt, userDetails)) {
UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(request, response);
}
catch (Exception err) {
handlerExceptionResolver.resolveException(request, response, null, err);
}
}
}
@@ -1,23 +1,52 @@
// package com.skycrate.backend.skycrateBackend.config;
package com.skycrate.backend.skycrateBackend.config;
import com.skycrate.backend.skycrateBackend.security.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
// import org.springframework.context.annotation.Bean;
// import org.springframework.context.annotation.Configuration;
// import org.springframework.security.config.annotation.web.builders.HttpSecurity;
// import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
// @Configuration
// public class SecurityConfig {
private final AuthenticationProvider authenticationProvider;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
// @Bean
// public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// http
// .csrf(csrf -> csrf.disable()) // Disable CSRF for testing APIs
// .authorizeHttpRequests(auth -> auth
// .requestMatchers("/api/hdfs/**").permitAll() // Allow HDFS endpoints
// .anyRequest().authenticated() // Everything else needs auth
// );
public SecurityConfig(AuthenticationProvider authenticationProvider,
JwtAuthenticationFilter jwtAuthenticationFilter) {
this.authenticationProvider = authenticationProvider;
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
// return http.build();
// }
// }
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/logout","/api/auth/login", "/api/auth/register", "/actuator/**").permitAll()
.requestMatchers(HttpMethod.GET, "/public/**").permitAll()
.anyRequest().authenticated()
)
.requiresChannel(channel -> channel
.anyRequest().requiresSecure()
)
.headers(headers -> headers
.httpStrictTransportSecurity(hsts -> hsts
.includeSubDomains(true)
.maxAgeInSeconds(31536000)
)
// Spring Security 6+ no longer supports xss.block(true), so we just enable or disable it.
.xssProtection(xss -> xss.disable())
.frameOptions(frame -> frame.deny())
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
@@ -1,105 +0,0 @@
package com.skycrate.backend.skycrateBackend.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
private final AuthenticationProvider authenticationProvider;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
public SecurityConfiguration(
JwtAuthenticationFilter jwtAuthenticationFilter,
AuthenticationProvider authenticationProvider
) {
this.authenticationProvider = authenticationProvider;
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
// @Bean
// public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// http.csrf()
// .disable()
// .authorizeHttpRequests()
// .requestMatchers("/api/hdfs/**") // Specific API endpoints that don't require authentication
// .permitAll()
// .requestMatchers("/api/**") // Other endpoints that should be open
// .permitAll()
// .anyRequest()
// .authenticated() // All other requests require authentication
// .and()
// .sessionManagement()
// .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// .and()
// .authenticationProvider(authenticationProvider)
// .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
//
// return http.build();
// }
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.securityMatcher("/**")
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/hdfs/**", "/api/**").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.csrf(csrf -> csrf.disable())
.cors(cors -> {}) // 🔥 This line enables CORS and connects to your CorsConfigurationSource bean
.build();
}
// @Bean
// CorsConfigurationSource corsConfigurationSource() {
// CorsConfiguration configuration = new CorsConfiguration();
//
// configuration.setAllowedOrigins(List.of("*"));
// configuration.setAllowedMethods(List.of("GET", "PUT", "DELETE", "POST"));
// configuration.setAllowedHeaders(List.of("Authorization", "Content-Type"));
//
// UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
//
// source.registerCorsConfiguration("/**", configuration);
//
// return source;
// }
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// 🔥 Allow all origins (wildcard) safely with credentials
configuration.setAllowedOriginPatterns(List.of("*"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setExposedHeaders(List.of("Authorization"));
configuration.setAllowCredentials(true); // Needed for cookies / Authorization headers
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
@@ -1,58 +1,134 @@
package com.skycrate.backend.skycrateBackend.controller;
import org.springframework.web.bind.annotation.RestController;
import com.skycrate.backend.skycrateBackend.dto.LoginUserDto;
import com.skycrate.backend.skycrateBackend.dto.LoginRequest;
import com.skycrate.backend.skycrateBackend.dto.LoginResponse;
import com.skycrate.backend.skycrateBackend.dto.RegisterUserDto;
import com.skycrate.backend.skycrateBackend.models.User;
import com.skycrate.backend.skycrateBackend.responses.LoginResponse;
import com.skycrate.backend.skycrateBackend.services.AuthenticationService;
import com.skycrate.backend.skycrateBackend.services.JwtService;
import org.springframework.beans.factory.annotation.Autowired;
import com.skycrate.backend.skycrateBackend.dto.TokenRefreshRequest;
import com.skycrate.backend.skycrateBackend.dto.TokenRefreshResponse;
import com.skycrate.backend.skycrateBackend.entity.RefreshToken;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.security.TokenBlacklistService;
import com.skycrate.backend.skycrateBackend.services.*;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.web.bind.annotation.*;
@RequestMapping("/api")
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private static final Logger log = LoggerFactory.getLogger(FileService.class);
private final AuthenticationManager authManager;
private final JwtService jwtService;
private AuthenticationService authenticationService;
private final UserRepository userRepository;
private final RefreshTokenService refreshTokenService;
private final TokenBlacklistService tokenBlacklistService;
private final RateLimiterService rateLimiterService;
private final AuthenticationService authenticationService;
public AuthController(JwtService jwtService,AuthenticationService authenticationService){
this.jwtService=jwtService;
this.authenticationService=authenticationService;
public AuthController(
AuthenticationManager authManager,
JwtService jwtService,
UserRepository userRepository,
RefreshTokenService refreshTokenService,
TokenBlacklistService tokenBlacklistService,
RateLimiterService rateLimiterService,
AuthenticationService authenticationService
) {
this.authManager = authManager;
this.jwtService = jwtService;
this.userRepository = userRepository;
this.refreshTokenService = refreshTokenService;
this.tokenBlacklistService = tokenBlacklistService;
this.rateLimiterService = rateLimiterService;
this.authenticationService = authenticationService;
}
@GetMapping("/test")
public String teString(@RequestParam String param) {
return new String();
// New Register Endpoint
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody RegisterUserDto request) {
User user = authenticationService.signUp(request);
return ResponseEntity.ok("User registered successfully with username: " + user.getUsername());
}
@PostMapping("/login")
public ResponseEntity<LoginResponse> LoginController(@RequestBody LoginUserDto entity) {
public ResponseEntity<?> login(@RequestBody LoginRequest request, HttpServletRequest servletRequest) {
String ip = servletRequest.getRemoteAddr();
User authenticatedUser=authenticationService.authenticate(entity);
String jwtToken=jwtService.generateToken(authenticatedUser);
if (rateLimiterService.isBlocked(ip)) {
return ResponseEntity.status(429).body("Too many login attempts. Please try again later.");
}
LoginResponse loginResponse=new LoginResponse().setToken(jwtToken).setExpiresIn(jwtService.getExpirtationTime());
return ResponseEntity.ok(loginResponse);
try {
authManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword())
);
} catch (Exception ex) {
rateLimiterService.recordFailedAttempt(ip);
return ResponseEntity.status(401).body("Invalid credentials.");
}
User user = userRepository.findByEmail(request.getEmail())
.orElseThrow(() -> new RuntimeException("User not found"));
rateLimiterService.resetAttempts(ip);
String accessToken = jwtService.generateToken(user);
RefreshToken refreshToken = refreshTokenService.createRefreshToken(user);
return ResponseEntity.ok(new LoginResponse(accessToken, refreshToken.getToken()));
}
@PostMapping("/signup")
public ResponseEntity<User> register(@RequestBody RegisterUserDto entity) {
User registeredUser=authenticationService.signUp(entity);
@PostMapping("/logout")
public ResponseEntity<?> logout(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return ResponseEntity.badRequest().body("Missing or invalid Authorization header");
}
String token = authHeader.substring(7);
String username = jwtService.extractUsername(token);
return ResponseEntity.ok(registeredUser);
userRepository.findByUsername(username).ifPresent(user -> {
// Clear the cached decrypted private key for the user
authenticationService.clearDecryptedPrivateKeyCache(user.getId().toString());
// Delete the refresh token associated with the user
refreshTokenService.logout(user); // This should delete the token
});
tokenBlacklistService.blacklistToken(token);
return ResponseEntity.ok("Logged out successfully");
}
@PostMapping("/refresh")
public ResponseEntity<?> refresh(@RequestBody TokenRefreshRequest request) {
String requestToken = request.getRefreshToken();
log.error("Received refresh token: " + requestToken);
return refreshTokenService.findByToken(requestToken)
.map(token -> {
if (refreshTokenService.isExpired(token)) {
log.error("Refresh token expired for user: " + token.getUser().getUsername());
// Clear the cached key on token expiry
authenticationService.clearDecryptedPrivateKeyCache(token.getUser().getId().toString());
return ResponseEntity.status(403).body("Refresh token expired");
}
User user = token.getUser();
String newAccessToken = jwtService.generateToken(user);
log.info("Generated new access token for user: " + user.getUsername());
return ResponseEntity.ok(new TokenRefreshResponse(newAccessToken, requestToken));
})
.orElseGet(() -> {
log.error("Invalid refresh token: " + requestToken);
return ResponseEntity.status(403).body("Invalid refresh token");
});
}
}
@@ -0,0 +1,73 @@
package com.skycrate.backend.skycrateBackend.controller;
import com.skycrate.backend.skycrateBackend.dto.FileDownloadRequest;
import com.skycrate.backend.skycrateBackend.services.FileService;
import com.skycrate.backend.skycrateBackend.services.JwtService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/api/files")
public class FileController {
private final FileService fileService;
private final JwtService jwtService;
public FileController(FileService fileService, JwtService jwtService) {
this.fileService = fileService;
this.jwtService = jwtService;
}
@PostMapping("/upload")
public ResponseEntity<?> uploadFile(
@RequestParam("file") MultipartFile file,
HttpServletRequest request) {
try {
String token = extractToken(request);
String username = jwtService.extractUsername(token);
fileService.uploadEncryptedFile(username, file.getBytes(), file.getOriginalFilename());
return ResponseEntity.ok("File uploaded and encrypted successfully.");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Upload failed: " + e.getMessage());
}
}
@GetMapping("/download")
public ResponseEntity<?> downloadFile(
@RequestBody FileDownloadRequest fileDownloadRequest,
HttpServletRequest request
) {
try {
String token = extractToken(request);
String username = jwtService.extractUsername(token);
// Use the password and filename from the FileDownloadRequest DTO
byte[] decryptedData = fileService.downloadDecryptedFile(username, fileDownloadRequest.getPassword(), fileDownloadRequest.getFilename());
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileDownloadRequest.getFilename() + "\"")
.contentLength(decryptedData.length)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(decryptedData);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("File download failed: " + e.getMessage());
}
}
private String extractToken(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new RuntimeException("Missing or invalid Authorization header");
}
return authHeader.substring(7);
}
}
@@ -2,9 +2,8 @@ package com.skycrate.backend.skycrateBackend.controller;
import com.skycrate.backend.skycrateBackend.config.HDFSConfig;
import com.skycrate.backend.skycrateBackend.dto.ResponseDTO;
import com.skycrate.backend.skycrateBackend.models.User;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.services.EncryptionUtil;
import com.skycrate.backend.skycrateBackend.services.HDFSOperations;
import com.skycrate.backend.skycrateBackend.utils.KeyUtil;
import com.skycrate.backend.skycrateBackend.utils.RSAKeyUtil;
@@ -36,9 +35,6 @@ import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.File; // For java.io.File
import static com.skycrate.backend.skycrateBackend.utils.KeyUtil.getPrivateKeyForUser;
import static com.skycrate.backend.skycrateBackend.utils.KeyUtil.getPublicKeyForUser;
@RestController
@RequestMapping("/api/hdfs")
public class HDFScontroller {
@@ -66,72 +62,71 @@ public class HDFScontroller {
}
}
@PostMapping("/uploadFile")
public ResponseDTO uploadFile(
@RequestParam("file") MultipartFile file,
@RequestParam String hdfsPath,
@RequestParam String uploadedFileName,
@RequestParam String username) {
try {
// Step 1: Retrieve the user and their RSA public key
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User not found"));
byte[] publicKeyBytes = user.getPublicKey();
PublicKey publicKey = RSAKeyUtil.getPublicKeyFromBytes(publicKeyBytes);
// @PostMapping("/uploadFile")
// public ResponseDTO uploadFile(
// @RequestParam("file") MultipartFile file,
// @RequestParam String hdfsPath,
// @RequestParam String uploadedFileName,
// @RequestParam String username) {
// try {
// // Retrieve the user from the database using the username
// User user = userRepository.findByUsername(username).orElseThrow(() -> new RuntimeException("User not found"));
//
// // Get the public key from the user entity
// byte[] publicKeyBytes = user.getPublicKey();
// PublicKey publicKey = RSAKeyUtil.getPublicKeyFromBytes(publicKeyBytes);
//
// // Encrypt the file content using the public key
// byte[] encryptedData = encryptFile(file, publicKey);
//
// // Upload the encrypted file to HDFS
// hdfsOperations.uploadFile(encryptedData, hdfsPath, uploadedFileName, username);
//
// return new ResponseDTO("File uploaded successfully", true);
// } catch (IOException e) {
// e.printStackTrace();
// return new ResponseDTO("Failed to upload file locally: " + e.getMessage(), false);
// } catch (Exception e) {
// e.printStackTrace();
// return new ResponseDTO("Failed to upload file to HDFS: " + e.getMessage(), false);
// }
// }
//
// // Helper method to encrypt the file content using RSA encryption
// private byte[] encryptFile(MultipartFile file, PublicKey publicKey) throws Exception {
// // Step 1: Generate a random AES key
// SecretKey aesKey = generateAESKey();
//
// // Step 2: Encrypt the file data using AES
// Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
// aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
// byte[] fileData = file.getBytes();
// byte[] encryptedData = aesCipher.doFinal(fileData);
//
// // Step 3: Encrypt the AES key with RSA
// Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
// rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
// byte[] encryptedAesKey = rsaCipher.doFinal(aesKey.getEncoded());
//
// // Step 4: Combine the encrypted AES key and the encrypted data
// byte[] combined = new byte[4 + encryptedAesKey.length + encryptedData.length];
// combined[0] = (byte) (encryptedAesKey.length >> 24);
// combined[1] = (byte) (encryptedAesKey.length >> 16);
// combined[2] = (byte) (encryptedAesKey.length >> 8);
// combined[3] = (byte) encryptedAesKey.length;
//
// System.arraycopy(encryptedAesKey, 0, combined, 4, encryptedAesKey.length);
// System.arraycopy(encryptedData, 0, combined, 4 + encryptedAesKey.length, encryptedData.length);
//
// return combined;
// }
// Step 2: Encrypt the file content using hybrid encryption (AES + RSA)
byte[] fileBytes = file.getBytes();
byte[] encryptedData = EncryptionUtil.encrypt(fileBytes, publicKey);
// Step 3: Upload encrypted data to HDFS
hdfsOperations.uploadFile(encryptedData, hdfsPath, uploadedFileName, username);
return new ResponseDTO("File uploaded successfully", true);
} catch (IOException e) {
e.printStackTrace();
return new ResponseDTO("Failed to read file: " + e.getMessage(), false);
} catch (Exception e) {
e.printStackTrace();
return new ResponseDTO("Failed to upload file to HDFS: " + e.getMessage(), false);
}
}
// Helper method to encrypt the file content using RSA encryption
private byte[] encryptFile(MultipartFile file, PublicKey publicKey) throws Exception {
// Step 1: Generate a random AES key
SecretKey aesKey = generateAESKey();
// Step 2: Encrypt the file data using AES
Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] fileData = file.getBytes();
byte[] encryptedData = aesCipher.doFinal(fileData);
// Step 3: Encrypt the AES key with RSA
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedAesKey = rsaCipher.doFinal(aesKey.getEncoded());
// Step 4: Combine the encrypted AES key and the encrypted data
byte[] combined = new byte[4 + encryptedAesKey.length + encryptedData.length];
combined[0] = (byte) (encryptedAesKey.length >> 24);
combined[1] = (byte) (encryptedAesKey.length >> 16);
combined[2] = (byte) (encryptedAesKey.length >> 8);
combined[3] = (byte) encryptedAesKey.length;
System.arraycopy(encryptedAesKey, 0, combined, 4, encryptedAesKey.length);
System.arraycopy(encryptedData, 0, combined, 4 + encryptedAesKey.length, encryptedData.length);
return combined;
}
// Generate a random AES key
private SecretKey generateAESKey() throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256); // Use 256 bits for AES
return keyGen.generateKey();
}
// // Generate a random AES key
// private SecretKey generateAESKey() throws NoSuchAlgorithmException {
// KeyGenerator keyGen = KeyGenerator.getInstance("AES");
// keyGen.init(256); // Use 256 bits for AES
// return keyGen.generateKey();
// }
private String saveFileLocally(MultipartFile file) throws IOException {
// Create a temporary directory if it doesn't exist
@@ -148,107 +143,107 @@ public class HDFScontroller {
return path.toString(); // Return the local path for further processing
}
@PostMapping("/downloadFile")
public ResponseEntity<Resource> downloadFile(
@RequestParam String hdfsEncPath,
@RequestParam String username) {
try {
// Extract the file name and extension
String encFileName = new File(hdfsEncPath).getName();
String originalFileName = encFileName.replace(".enc", "");
String fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".") + 1);
// Define local decrypted file path
String localDecryptedPath = "/SkyCrate/downloaded/" + originalFileName;
// Define HDFS paths for encrypted file
String encFilePath = "/SkyCrate/downloaded/" + encFileName;
FileSystem fs = HDFSConfig.getHDFS();
// Download encrypted file from HDFS
fs.copyToLocalFile(new org.apache.hadoop.fs.Path(hdfsEncPath), new org.apache.hadoop.fs.Path(encFilePath));
// Retrieve the RSA private key for the user
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User not found"));
PrivateKey privateKey = RSAKeyUtil.getPrivateKeyFromBytes(user.getPrivateKey());
// Read the encrypted file content
byte[] encryptedFileContent = Files.readAllBytes(Paths.get(encFilePath));
// Step 1: Extract the AES key length from the combined data
int aesKeyLength = ((encryptedFileContent[0] & 0xFF) << 24) |
((encryptedFileContent[1] & 0xFF) << 16) |
((encryptedFileContent[2] & 0xFF) << 8) |
(encryptedFileContent[3] & 0xFF);
// Step 2: Extract the encrypted AES key and encrypted data
byte[] encryptedAesKey = new byte[aesKeyLength];
byte[] encryptedData = new byte[encryptedFileContent.length - 4 - aesKeyLength];
System.arraycopy(encryptedFileContent, 4, encryptedAesKey, 0, aesKeyLength);
System.arraycopy(encryptedFileContent, 4 + aesKeyLength, encryptedData, 0, encryptedData.length);
// Step 3: Decrypt the AES key using RSA
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] aesKeyBytes = rsaCipher.doFinal(encryptedAesKey);
// Create the AES key
SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES");
// Step 4: Decrypt the data using AES
Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
aesCipher.init(Cipher.DECRYPT_MODE, aesKey);
// Decrypt the file content using the provided decrypt method
// byte[] decryptedFileContent = RSAKeyUtil.decrypt(encryptedFileContent, privateKey);
byte[] decryptedFileContent = aesCipher.doFinal(encryptedData);
// Write the decrypted content to the original file
Files.write(Paths.get(localDecryptedPath + "." + fileExtension), decryptedFileContent);
// Log the file creation
if (Files.exists(Paths.get(localDecryptedPath + "." + fileExtension))) {
System.out.println("File created successfully at: " + localDecryptedPath + "." + fileExtension);
} else {
System.out.println("Failed to create file at: " + localDecryptedPath + "." + fileExtension);
}
// Create the decrypted file resource
File decryptedFile = new File(localDecryptedPath + "." + fileExtension);
Resource resource = new FileSystemResource(decryptedFile);
// Return the file as a response
return ResponseEntity.ok()
.contentLength(decryptedFile.length())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + decryptedFile.getName() + "\"")
.body(resource);
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(null);
}
}
public void initializeKeysForUser(String username) {
try {
// Check if the public key file exists
Path publicKeyPath = Paths.get("C:\\Users\\sonal\\OneDrive\\Desktop\\SkyCrate\\Skycrate\\keys", username + "_public.key");
if (!Files.exists(publicKeyPath)) {
// Generate and store keys if they do not exist
KeyUtil.generateAndStoreKeyPair(username);
}
} catch (Exception e) {
e.printStackTrace();
}
}
// @PostMapping("/downloadFile")
// public ResponseEntity<Resource> downloadFile(
// @RequestParam String hdfsEncPath,
// @RequestParam String username) {
// try {
// // Extract the file name and extension
// String encFileName = new File(hdfsEncPath).getName();
// String originalFileName = encFileName.replace(".enc", "");
// String fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".") + 1);
//
// // Define local decrypted file path
// String localDecryptedPath = "/SkyCrate/downloaded/" + originalFileName;
//
// // Define HDFS paths for encrypted file
// String encFilePath = "/SkyCrate/downloaded/" + encFileName;
//
// FileSystem fs = HDFSConfig.getHDFS();
//
// // Download encrypted file from HDFS
// fs.copyToLocalFile(new org.apache.hadoop.fs.Path(hdfsEncPath), new org.apache.hadoop.fs.Path(encFilePath));
//
// // Retrieve the RSA private key for the user
// User user = userRepository.findByUsername(username)
// .orElseThrow(() -> new RuntimeException("User not found"));
// PrivateKey privateKey = RSAKeyUtil.getPrivateKeyFromBytes(user.getPrivateKey());
//
// // Read the encrypted file content
// byte[] encryptedFileContent = Files.readAllBytes(Paths.get(encFilePath));
//
// // Step 1: Extract the AES key length from the combined data
// int aesKeyLength = ((encryptedFileContent[0] & 0xFF) << 24) |
// ((encryptedFileContent[1] & 0xFF) << 16) |
// ((encryptedFileContent[2] & 0xFF) << 8) |
// (encryptedFileContent[3] & 0xFF);
//
// // Step 2: Extract the encrypted AES key and encrypted data
// byte[] encryptedAesKey = new byte[aesKeyLength];
// byte[] encryptedData = new byte[encryptedFileContent.length - 4 - aesKeyLength];
//
// System.arraycopy(encryptedFileContent, 4, encryptedAesKey, 0, aesKeyLength);
// System.arraycopy(encryptedFileContent, 4 + aesKeyLength, encryptedData, 0, encryptedData.length);
//
// // Step 3: Decrypt the AES key using RSA
// Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
// rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
// byte[] aesKeyBytes = rsaCipher.doFinal(encryptedAesKey);
//
// // Create the AES key
// SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES");
//
// // Step 4: Decrypt the data using AES
// Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
// aesCipher.init(Cipher.DECRYPT_MODE, aesKey);
//
// // Decrypt the file content using the provided decrypt method
//// byte[] decryptedFileContent = RSAKeyUtil.decrypt(encryptedFileContent, privateKey);
// byte[] decryptedFileContent = aesCipher.doFinal(encryptedData);
//
// // Write the decrypted content to the original file
// Files.write(Paths.get(localDecryptedPath + "." + fileExtension), decryptedFileContent);
//
//
// // Log the file creation
// if (Files.exists(Paths.get(localDecryptedPath + "." + fileExtension))) {
// System.out.println("File created successfully at: " + localDecryptedPath + "." + fileExtension);
// } else {
// System.out.println("Failed to create file at: " + localDecryptedPath + "." + fileExtension);
// }
//
// // Create the decrypted file resource
// File decryptedFile = new File(localDecryptedPath + "." + fileExtension);
// Resource resource = new FileSystemResource(decryptedFile);
//
// // Return the file as a response
// return ResponseEntity.ok()
// .contentLength(decryptedFile.length())
// .contentType(MediaType.APPLICATION_OCTET_STREAM)
// .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + decryptedFile.getName() + "\"")
// .body(resource);
//
// } catch (Exception e) {
// e.printStackTrace();
// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
// .body(null);
// }
// }
//
//
// public void initializeKeysForUser(String username) {
// try {
// // Check if the public key file exists
// Path publicKeyPath = Paths.get("C:\\Users\\sonal\\OneDrive\\Desktop\\SkyCrate\\Skycrate\\keys", username + "_public.key");
// if (!Files.exists(publicKeyPath)) {
// // Generate and store keys if they do not exist
// KeyUtil.generateAndStoreKeyPair(username);
// }
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
//
@DeleteMapping("/deleteFile")
public ResponseDTO deleteFile(@RequestParam String hdfsPath) {
@@ -280,4 +275,25 @@ public class HDFScontroller {
.body("Failed to list files: " + e.getMessage());
}
}
@GetMapping("/getUsernameByEmail")
public ResponseEntity<?> getUsernameByEmail(@RequestParam String email) {
try {
// Fetch user from the database using the provided email
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new RuntimeException("User not found with email: " + email));
// // Log the retrieved user object to verify the username
// System.out.println("Retrieved user: " + user.getFullname());
// Return the username as the response
return ResponseEntity.ok(user.getFullname()); // Return the username
} catch (Exception e) {
// Handle error if user is not found or other exceptions occur
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Failed to fetch username: " + e.getMessage());
}
}
}
@@ -0,0 +1,23 @@
package com.skycrate.backend.skycrateBackend.dto;
public class FileDownloadRequest {
private String filename;
private String password;
// Getters and Setters
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
@@ -0,0 +1,13 @@
package com.skycrate.backend.skycrateBackend.dto;
public class LoginRequest {
private String email;
private String password;
// Getters and setters
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
@@ -0,0 +1,17 @@
package com.skycrate.backend.skycrateBackend.dto;
public class LoginResponse {
private String accessToken;
private String refreshToken;
private String tokenType = "Bearer";
public LoginResponse(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
// Getters
public String getAccessToken() { return accessToken; }
public String getRefreshToken() { return refreshToken; }
public String getTokenType() { return tokenType; }
}
@@ -1,46 +1,46 @@
package com.skycrate.backend.skycrateBackend.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
public class RegisterUserDto {
@NotBlank(message = "Email is required")
@Email(message = "Email should be valid")
private String email;
@NotBlank(message = "Password is required")
@Size(min = 8, message = "Password must be at least 8 characters")
private String password;
@NotBlank(message = "Username is required")
@Pattern(regexp = "^[a-zA-Z0-9]+$", message = "Username must be alphanumeric")
private String username;
@NotBlank(message = "Full name is required")
private String fullname;
@NotBlank(message = "First name is required")
private String firstname;
@NotBlank(message = "Last name is required")
private String lastname;
public String getEmail() {
return email;
}
// Getters
public String getEmail() { return email; }
public String getPassword() { return password; }
public String getUsername() { return username; }
public String getFullname() { return fullname; }
public String getFirstname() { return firstname; }
public String getLastname() { return lastname; }
public RegisterUserDto setEmail(String email) {
this.email = email;
return this;
}
public String getPassword() {
return password;
}
public RegisterUserDto setPassword(String password) {
this.password = password;
return this;
}
public String getFirstname() {
return firstname;
}
public RegisterUserDto setFirstname(String firstname) {
this.firstname = firstname;
return this;
}
public String getLastname() {
return lastname;
}
public RegisterUserDto setLastname(String lastname) {
this.lastname = lastname;
return this;
}
// Setters
public void setEmail(String email) { this.email = email; }
public void setPassword(String password) { this.password = password; }
public void setUsername(String username) { this.username = username; }
public void setFullname(String fullname) { this.fullname = fullname; }
public void setFirstname(String firstname) { this.firstname = firstname; }
public void setLastname(String lastname) { this.lastname = lastname; }
}
@@ -0,0 +1,33 @@
package com.skycrate.backend.skycrateBackend.dto;
public class SignupRequest {
private String username;
private String email;
private String password;
// Getters
public String getUsername() {
return username;
}
public String getEmail() {
return email;
}
public String getPassword() {
return password;
}
// Setters
public void setUsername(String username) {
this.username = username;
}
public void setEmail(String email) {
this.email = email;
}
public void setPassword(String password) {
this.password = password;
}
}
@@ -0,0 +1,13 @@
package com.skycrate.backend.skycrateBackend.dto;
public class TokenRefreshRequest {
private String refreshToken;
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
}
@@ -0,0 +1,17 @@
package com.skycrate.backend.skycrateBackend.dto;
public class TokenRefreshResponse {
private String accessToken;
private String refreshToken;
private String tokenType = "Bearer";
public TokenRefreshResponse(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
// Getters
public String getAccessToken() { return accessToken; }
public String getRefreshToken() { return refreshToken; }
public String getTokenType() { return tokenType; }
}
@@ -1,23 +0,0 @@
package com.skycrate.backend.skycrateBackend.dto;
import com.skycrate.backend.skycrateBackend.services.EncryptionUtil;
import java.security.KeyPair;
public class User {
private String username;
private KeyPair keyPair;
public User(String username) throws Exception {
this.username = username;
this.keyPair = EncryptionUtil.generateKeyPair();
}
public String getUsername() {
return username;
}
public KeyPair getKeyPair() {
return keyPair;
}
}
@@ -0,0 +1,39 @@
package com.skycrate.backend.skycrateBackend.entity;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "file_metadata")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FileMetadata {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String filePath;
@Column(nullable = false)
private String username;
@Lob
@Column(nullable = false)
private byte[] salt;
@Lob
@Column(nullable = false)
private byte[] iv;
@Lob
@Column(nullable = false, name = "encrypted_key", columnDefinition = "LONGBLOB")
private byte[] encryptedKey;
@Column(nullable = false)
private long uploadedAt;
}
@@ -0,0 +1,33 @@
package com.skycrate.backend.skycrateBackend.entity;
import jakarta.persistence.*;
import java.time.Instant;
@Entity
public class RefreshToken {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String token;
@OneToOne
@JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
private Instant expiryDate;
// Getters and setters
public Long getId() { return id; }
public String getToken() { return token; }
public void setToken(String token) { this.token = token; }
public User getUser() { return user; }
public void setUser(User user) { this.user = user; }
public Instant getExpiryDate() { return expiryDate; }
public void setExpiryDate(Instant expiryDate) { this.expiryDate = expiryDate; }
}
@@ -0,0 +1,90 @@
package com.skycrate.backend.skycrateBackend.entity;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String fullname;
@Lob
private byte[] publicKey;
@Lob
private byte[] privateKey;
@Lob
@Column(nullable = false)
private byte[] privateKeySalt;
@Lob
@Column(nullable = false)
private byte[] privateKeyIv;
@Builder
public User(String email, String password, String username, String fullname,
byte[] publicKey, byte[] privateKey,
byte[] privateKeySalt, byte[] privateKeyIv) {
this.email = email;
this.password = password;
this.username = username;
this.fullname = fullname;
this.publicKey = publicKey;
this.privateKey = privateKey;
this.privateKeySalt = privateKeySalt;
this.privateKeyIv = privateKeyIv;
}
// --- UserDetails interface methods ---
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(); // No roles assigned currently
}
@Override
public String getUsername() {
return username;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public boolean isAccountNonExpired() { return true; }
@Override
public boolean isAccountNonLocked() { return true; }
@Override
public boolean isCredentialsNonExpired() { return true; }
@Override
public boolean isEnabled() { return true; }
}
@@ -1,139 +0,0 @@
package com.skycrate.backend.skycrateBackend.models;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.hibernate.annotations.CreationTimestamp;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import jakarta.persistence.*;
@Table(name = "users")
@Entity
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(nullable = false)
private Integer id;
@Column(nullable = false)
private String username;
@Column(unique = true, nullable = false)
private String email;
@Column(nullable = false)
private String password;
// ✅ Add RSA key fields
@Lob
@Column(name = "public_key", columnDefinition = "BLOB")
private byte[] publicKey;
@Lob
@Column(name = "private_key", columnDefinition = "BLOB")
private byte[] privateKey;
@CreationTimestamp
@Column(updatable = false, name = "created_at")
private Date createdAt;
public User() {}
public User(String firstname, String lastname, String email, String password) {
this.username = firstname + lastname;
this.email = email;
this.password = password;
}
// ⬇️ Required by Spring Security
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of();
}
@Override
public String getUsername() {
return email;
}
@Override
public String getPassword() {
return password;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
// ⬇️ Getters and Setters
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public void setFullname(String firstname, String lastname) {
this.username = firstname + lastname;
}
public String getFullname() {
return this.username;
}
public void setEmail(String email) {
this.email = email;
}
public void setPassword(String password) {
this.password = password;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
// ✅ Getters and setters for RSA keys
public byte[] getPublicKey() {
return publicKey;
}
public void setPublicKey(byte[] publicKey) {
this.publicKey = publicKey;
}
public byte[] getPrivateKey() {
return privateKey;
}
public void setPrivateKey(byte[] privateKey) {
this.privateKey = privateKey;
}
}
@@ -0,0 +1,10 @@
package com.skycrate.backend.skycrateBackend.repository;
import com.skycrate.backend.skycrateBackend.entity.FileMetadata;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface FileMetadataRepository extends JpaRepository<FileMetadata, Long> {
Optional<FileMetadata> findByUsernameAndFilePath(String username, String filePath);
}
@@ -0,0 +1,19 @@
package com.skycrate.backend.skycrateBackend.repository;
import com.skycrate.backend.skycrateBackend.entity.RefreshToken;
import com.skycrate.backend.skycrateBackend.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
Optional<RefreshToken> findByToken(String token);
@Transactional
@Modifying
@Query("DELETE FROM RefreshToken t WHERE t.user = :user")
void deleteByUser(User user);
}
@@ -1,25 +0,0 @@
package com.skycrate.backend.skycrateBackend.repository;
import com.skycrate.backend.skycrateBackend.dto.User;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserManager {
private Map<String, User> users = new HashMap<>();
public User getUser(String username) throws Exception {
if (!users.containsKey(username)) {
users.put(username, new User(username));
}
return users.get(username);
}
public boolean authenticate(String username, String password) {
// Implement your authentication logic here
return "admin".equals(username) && "password123".equals(password);
}
}
@@ -2,7 +2,7 @@ package com.skycrate.backend.skycrateBackend.repository;
import java.util.Optional;
import org.springframework.data.repository.CrudRepository;
import com.skycrate.backend.skycrateBackend.models.User;
import com.skycrate.backend.skycrateBackend.entity.User;
public interface UserRepository extends CrudRepository<User,Integer> {
Optional<User> findByEmail(String email);
// Custom query method to find user by username
@@ -0,0 +1,42 @@
package com.skycrate.backend.skycrateBackend.security;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.*;
import java.security.SecureRandom;
import java.util.Base64;
public class EncryptionService {
private static final int KEY_LENGTH = 256;
private static final int ITERATIONS = 65536;
private static final int SALT_LENGTH = 16;
private static final int IV_LENGTH = 12;
public static byte[] generateSalt() {
byte[] salt = new byte[SALT_LENGTH];
new SecureRandom().nextBytes(salt);
return salt;
}
public static SecretKey deriveKey(String password, byte[] salt) throws Exception {
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
}
public static byte[] encrypt(byte[] plaintext, SecretKey key, byte[] iv) throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
return cipher.doFinal(plaintext);
}
public static byte[] decrypt(byte[] ciphertext, SecretKey key, byte[] iv) throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
return cipher.doFinal(ciphertext);
}
}
@@ -0,0 +1,90 @@
package com.skycrate.backend.skycrateBackend.security;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.services.JwtService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserRepository userRepository;
private final TokenBlacklistService tokenBlacklistService;
public JwtAuthenticationFilter(JwtService jwtService,
UserRepository userRepository,
TokenBlacklistService tokenBlacklistService) {
this.jwtService = jwtService;
this.userRepository = userRepository;
this.tokenBlacklistService = tokenBlacklistService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String path = request.getRequestURI();
if (path.startsWith("/api/auth") || path.startsWith("/actuator")) {
filterChain.doFilter(request, response);
return;
}
final String authHeader = request.getHeader("Authorization");
final String jwt;
final String username;
if (!StringUtils.hasText(authHeader) || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
jwt = authHeader.substring(7);
// Check if token is blacklisted
if (tokenBlacklistService.isTokenBlacklisted(jwt)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Token has been blacklisted");
return;
}
try {
username = jwtService.extractUsername(jwt); // This is actually the `username`, not email
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Invalid JWT token");
return;
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// ❗ Use username to find the user
User user = userRepository.findByUsername(username).orElse(null);
if (user != null && jwtService.isTokenValid(jwt, user)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
user, null, user.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
} else {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Expired or invalid JWT");
return;
}
}
filterChain.doFilter(request, response);
}
}
@@ -0,0 +1,27 @@
package com.skycrate.backend.skycrateBackend.security;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class TokenBlacklistService {
private final Cache<String, Boolean> blacklistCache;
public TokenBlacklistService() {
this.blacklistCache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.HOURS)
.build();
}
public void blacklistToken(String token) {
blacklistCache.put(token, true);
}
public boolean isTokenBlacklisted(String token) {
return blacklistCache.getIfPresent(token) != null;
}
}
@@ -1,18 +1,26 @@
package com.skycrate.backend.skycrateBackend.services;
import com.skycrate.backend.skycrateBackend.config.HDFSConfig;
import com.skycrate.backend.skycrateBackend.dto.LoginUserDto;
import com.skycrate.backend.skycrateBackend.dto.RegisterUserDto;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.utils.EncryptionUtil;
import com.skycrate.backend.skycrateBackend.utils.RSAKeyUtil;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.skycrate.backend.skycrateBackend.dto.LoginUserDto;
import com.skycrate.backend.skycrateBackend.dto.RegisterUserDto;
import com.skycrate.backend.skycrateBackend.models.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.utils.RSAKeyUtil;
import javax.crypto.SecretKey;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class AuthenticationService {
@@ -20,30 +28,67 @@ public class AuthenticationService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final AuthenticationManager authenticationManager;
private final KeyCacheService keyCacheService;
public AuthenticationService(UserRepository userRepository, AuthenticationManager authenticationManager, PasswordEncoder passwordEncoder) {
private static final Logger log = LoggerFactory.getLogger(AuthenticationService.class);
public AuthenticationService(UserRepository userRepository,
AuthenticationManager authenticationManager,
PasswordEncoder passwordEncoder,
KeyCacheService keyCacheService) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.authenticationManager = authenticationManager;
this.keyCacheService = keyCacheService;
}
public User signUp(RegisterUserDto inputUser) {
User user = new User(
inputUser.getFirstname(),
inputUser.getLastname(),
inputUser.getEmail(),
passwordEncoder.encode(inputUser.getPassword())
);
// Generate RSA key pair
KeyPair keyPair;
try {
KeyPair keyPair = RSAKeyUtil.generateKeyPair();
user.setPublicKey(keyPair.getPublic().getEncoded());
user.setPrivateKey(keyPair.getPrivate().getEncoded());
keyPair = RSAKeyUtil.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Failed to generate RSA key pair", e);
}
return userRepository.save(user);
// Encrypt private key using password-derived AES key
byte[] salt = EncryptionUtil.generateSalt();
byte[] iv = EncryptionUtil.generateIv();
byte[] encryptedPrivateKey;
try {
SecretKey aesKey = EncryptionUtil.deriveKey(inputUser.getPassword().toCharArray(), salt);
encryptedPrivateKey = EncryptionUtil.encrypt(keyPair.getPrivate().getEncoded(), aesKey, iv);
} catch (Exception e) {
throw new RuntimeException("Failed to encrypt private key", e);
}
// Create user entity with encrypted private key, salt, and iv
User user = User.builder()
.fullname(inputUser.getFirstname() + " " + inputUser.getLastname())
.username(inputUser.getUsername())
.email(inputUser.getEmail())
.password(passwordEncoder.encode(inputUser.getPassword()))
.publicKey(keyPair.getPublic().getEncoded())
.privateKey(encryptedPrivateKey)
.privateKeySalt(salt)
.privateKeyIv(iv)
.build();
// Save user
User savedUser = userRepository.save(user);
// Create HDFS directory in root with username
try {
FileSystem fs = HDFSConfig.getHDFS();
Path userDir = new Path("/" + savedUser.getUsername());
if (!fs.exists(userDir)) {
fs.mkdirs(userDir);
}
} catch (Exception e) {
throw new RuntimeException("Failed to create HDFS directory for user: " + savedUser.getUsername(), e);
}
return savedUser;
}
public User authenticate(LoginUserDto inputUser) {
@@ -54,4 +99,23 @@ public class AuthenticationService {
return userRepository.findByEmail(inputUser.getEmail())
.orElseThrow(() -> new RuntimeException("User not found"));
}
@Cacheable(value = "decryptedPrivateKeys", key = "#userId")
public byte[] getDecryptedPrivateKey(String userId, String password) throws Exception {
User user = userRepository.findById(Integer.valueOf(userId))
.orElseThrow(() -> new RuntimeException("User not found: " + userId));
log.info("Caching decrypted private key for userId: {}", userId);
SecretKey derivedKey = EncryptionUtil.deriveKey(password.toCharArray(), user.getPrivateKeySalt());
byte[] decryptedPrivateKeyBytes = EncryptionUtil.decrypt(user.getPrivateKey(), derivedKey, user.getPrivateKeyIv());
return decryptedPrivateKeyBytes;
}
@CacheEvict(value = "decryptedPrivateKeys", key = "#userId")
public void clearDecryptedPrivateKeyCache(String userId) {
// This method will clear the cached decrypted private key for the given userId
log.info("Clearing Caching decrypted private key for userId: {}", userId);
keyCacheService.clearKey(Long.valueOf(userId));
}
}
@@ -1,87 +1,79 @@
package com.skycrate.backend.skycrateBackend.services;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Arrays;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
public class EncryptionUtil {
private static final String RSA_ALGORITHM = "RSA";
private static final String AES_ALGORITHM = "AES";
private static final int RSA_KEY_SIZE = 2048;
private static final int AES_KEY_SIZE = 256;
// Generate RSA Key Pair (Public & Private)
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA_ALGORITHM);
keyGen.initialize(RSA_KEY_SIZE);
return keyGen.generateKeyPair();
private static final int SALT_LENGTH = 16; // in bytes
private static final int IV_LENGTH = 16; // for AES CBC
private static final int ITERATIONS = 65536;
private static final int KEY_LENGTH = 256; // bits
// --- AES key derivation using PBKDF2 ---
public static SecretKey deriveAESKey(char[] password, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
byte[] keyBytes = factory.generateSecret(spec).getEncoded();
return new SecretKeySpec(keyBytes, "AES");
}
// Encrypt data using AES (AES Key is encrypted using RSA)
public static byte[] encrypt(byte[] data, PublicKey publicKey) throws Exception {
// Step 1: Generate AES Key
SecretKey aesKey = generateAESKey();
// --- Encrypt data using AES-CBC ---
public static byte[] encrypt(byte[] data, SecretKey key, byte[] iv)
throws GeneralSecurityException {
// Encrypt data using AES
Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] encryptedData = aesCipher.doFinal(data);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// Encrypt the AES key with RSA
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedAesKey = rsaCipher.doFinal(aesKey.getEncoded());
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
// Step 4: Combine encrypted AES key and encrypted data into one array
byte[] combined = new byte[4 + encryptedAesKey.length + encryptedData.length];
// First 4 bytes indicate the length of the AES encrypted key
combined[0] = (byte) (encryptedAesKey.length >> 24);
combined[1] = (byte) (encryptedAesKey.length >> 16);
combined[2] = (byte) (encryptedAesKey.length >> 8);
combined[3] = (byte) encryptedAesKey.length;
// Copy AES Key and Encrypted Data into the combined array
System.arraycopy(encryptedAesKey, 0, combined, 4, encryptedAesKey.length);
System.arraycopy(encryptedData, 0, combined, 4 + encryptedAesKey.length, encryptedData.length);
return combined;
return cipher.doFinal(data);
}
// Decrypt data using RSA (AES Key is decrypted using RSA, then used for AES decryption)
public static byte[] decrypt(byte[] encryptedCombined, PrivateKey privateKey) throws Exception {
// Step 1: Extract AES Key length from the combined data
int aesKeyLength = ((encryptedCombined[0] & 0xFF) << 24) |
((encryptedCombined[1] & 0xFF) << 16) |
((encryptedCombined[2] & 0xFF) << 8) |
(encryptedCombined[3] & 0xFF);
// --- Decrypt data using AES-CBC ---
public static byte[] decrypt(byte[] encryptedData, SecretKey key, byte[] iv)
throws GeneralSecurityException {
// Step 2: Extract the encrypted AES key and encrypted data
byte[] encryptedAesKey = new byte[aesKeyLength];
byte[] encryptedData = new byte[encryptedCombined.length - 4 - aesKeyLength];
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
System.arraycopy(encryptedCombined, 4, encryptedAesKey, 0, aesKeyLength);
System.arraycopy(encryptedCombined, 4 + aesKeyLength, encryptedData, 0, encryptedData.length);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
// Step 3: Decrypt the AES key using RSA
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] aesKeyBytes = rsaCipher.doFinal(encryptedAesKey);
// Create AES key
SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES");
// Decrypt the data using AES
Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
aesCipher.init(Cipher.DECRYPT_MODE, aesKey);
return aesCipher.doFinal(encryptedData);
return cipher.doFinal(encryptedData);
}
// Generate a random AES key
private static SecretKey generateAESKey() throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance(AES_ALGORITHM);
keyGen.init(AES_KEY_SIZE);
return keyGen.generateKey();
// --- Generate random salt ---
public static byte[] generateSalt() {
byte[] salt = new byte[SALT_LENGTH];
new SecureRandom().nextBytes(salt);
return salt;
}
// --- Generate random IV ---
public static byte[] generateIV() {
byte[] iv = new byte[IV_LENGTH];
new SecureRandom().nextBytes(iv);
return iv;
}
// --- Optional: Utility to base64 encode data ---
public static String encodeBase64(byte[] data) {
return Base64.getEncoder().encodeToString(data);
}
public static byte[] decodeBase64(String base64) {
return Base64.getDecoder().decode(base64);
}
}
@@ -0,0 +1,119 @@
package com.skycrate.backend.skycrateBackend.services;
import com.skycrate.backend.skycrateBackend.config.HDFSConfig;
import com.skycrate.backend.skycrateBackend.entity.FileMetadata;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.FileMetadataRepository;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.utils.EncryptionUtil;
import com.skycrate.backend.skycrateBackend.utils.RSAKeyUtil;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.crypto.SecretKey;
import java.io.ByteArrayInputStream;
import java.security.PrivateKey;
import java.security.PublicKey;
@Service
public class FileService {
private static final Logger log = LoggerFactory.getLogger(FileService.class);
private final AuthenticationService authenticationService;
private final FileMetadataRepository fileMetadataRepository;
private final UserRepository userRepository;
@Autowired
public FileService(FileMetadataRepository fileMetadataRepository, UserRepository userRepository, AuthenticationService authenticationService) {
this.fileMetadataRepository = fileMetadataRepository;
this.userRepository = userRepository;
this.authenticationService = authenticationService;
}
@Transactional
public void uploadEncryptedFile(String username, byte[] fileContent, String filename) throws Exception {
log.info("Starting upload for user={}, file={}", username, filename);
try {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User not found: " + username));
SecretKey aesKey = EncryptionUtil.generateAESKey();
byte[] salt = EncryptionUtil.generateSalt(); // reserved for future use
byte[] iv = EncryptionUtil.generateIv();
byte[] encryptedData = EncryptionUtil.encrypt(fileContent, aesKey, iv);
PublicKey publicKey = RSAKeyUtil.decodePublicKey(user.getPublicKey());
byte[] encryptedAesKey = EncryptionUtil.encryptRSA(aesKey.getEncoded(), publicKey);
Path userDir = new Path("/" + username);
Path filePath = new Path(userDir, filename);
FileSystem fs = HDFSConfig.getHDFS();
if (!fs.exists(userDir)) {
log.info("Creating directory in HDFS: {}", userDir);
fs.mkdirs(userDir);
}
log.info("Writing encrypted file to HDFS: {}", filePath);
try (FSDataOutputStream out = fs.create(filePath, true);
ByteArrayInputStream in = new ByteArrayInputStream(encryptedData)) {
in.transferTo(out);
}
FileMetadata metadata = FileMetadata.builder()
.username(username)
.filePath(filePath.toString())
.salt(salt)
.iv(iv)
.encryptedKey(encryptedAesKey)
.uploadedAt(System.currentTimeMillis())
.build();
fileMetadataRepository.save(metadata);
log.info("Upload complete: file={} for user={}", filename, username);
} catch (Exception e) {
log.error("Error during file upload for user={}, file={}: {}", username, filename, e.getMessage(), e);
throw e;
}
}
public byte[] downloadDecryptedFile(String username, String password, String filename) throws Exception {
log.info("Download request: user={}, file={}", username, filename);
try {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User not found: " + username));
Path filePath = new Path("/" + username + "/" + filename);
FileMetadata metadata = fileMetadataRepository.findByUsernameAndFilePath(username, filePath.toString())
.orElseThrow(() -> new RuntimeException("File metadata not found for: " + filePath));
// Use the cached decrypted private key
byte[] decryptedPrivateKeyBytes = authenticationService.getDecryptedPrivateKey(String.valueOf(user.getId()), password);
PrivateKey privateKey = RSAKeyUtil.decodePrivateKey(decryptedPrivateKeyBytes);
byte[] aesKeyBytes = EncryptionUtil.decryptRSA(metadata.getEncryptedKey(), privateKey);
SecretKey aesKey = EncryptionUtil.rebuildAESKey(aesKeyBytes);
FileSystem fs = HDFSConfig.getHDFS();
byte[] encryptedData;
try (FSDataInputStream in = fs.open(filePath)) {
encryptedData = in.readAllBytes();
}
return EncryptionUtil.decrypt(encryptedData, aesKey, metadata.getIv());
} catch (Exception e) {
log.error("Download failed for user={}, file={}: {}", username, filename, e.getMessage(), e);
throw e;
}
}
}
@@ -1,7 +1,7 @@
package com.skycrate.backend.skycrateBackend.services;
import com.skycrate.backend.skycrateBackend.config.HDFSConfig;
import com.skycrate.backend.skycrateBackend.models.User;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.utils.RSAKeyUtil;
import org.apache.hadoop.fs.FSDataOutputStream;
@@ -39,86 +39,122 @@ public class HDFSOperations {
this.userRepository = userRepository;
}
public void uploadFile(byte[] fileData, String hdfsPath, String uploadedFileName, String username) {
try {
FileSystem fs = HDFSConfig.getHDFS();
// public void uploadFile(byte[] fileData, String hdfsPath, String uploadedFileName, String username) {
// try {
// FileSystem fs = HDFSConfig.getHDFS();
//
// // Create an InputStream from the byte array
// ByteArrayInputStream inputStream = new ByteArrayInputStream(fileData);
//
// // Prepare the path for HDFS
// String finalHdfsPath = hdfsPath.endsWith("/") ? hdfsPath + uploadedFileName : hdfsPath + "/" + uploadedFileName;
//
// // Upload the file directly to HDFS from the InputStream
// Path hdfsFilePath = new Path(finalHdfsPath);
// FSDataOutputStream outputStream = fs.create(hdfsFilePath);
// IOUtils.copyBytes(inputStream, outputStream, 4096, true);
//
// } catch (IOException e) {
// // Handle I/O exception and log the error
// throw new RuntimeException("Failed to upload file to HDFS: " + e.getMessage(), e);
// } catch (Exception e) {
// // Catch any other exceptions
// throw new RuntimeException("Failed to upload file to HDFS: " + e.getMessage(), e);
// }
// }
//
// public void downloadFile(String hdfsEncPath, String localPathWithoutExt, String username) {
// try {
// FileSystem fs = HDFSConfig.getHDFS();
//
// // Extract file name and extension
// String encFileName = new File(hdfsEncPath).getName();
// String originalFileName = encFileName.replace(".enc", "");
// String fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".") + 1);
//
// String fullDecryptedPath = localPathWithoutExt + "/" + originalFileName;
// String encFilePath = fullDecryptedPath + ".enc";
// String keyFilePath = fullDecryptedPath + ".key";
//
// // Download encrypted file and AES key from HDFS
// fs.copyToLocalFile(new Path(hdfsEncPath), new Path(encFilePath));
// fs.copyToLocalFile(new Path(hdfsEncPath.replace(".enc", ".key")), new Path(keyFilePath));
//
// // Read the encrypted AES key
// byte[] encryptedAesKey = Files.readAllBytes(Paths.get(keyFilePath));
// System.out.println("Length of encrypted AES key: " + encryptedAesKey.length);
//
// // Retrieve the RSA private key for the user
// User user = userRepository.findByUsername(username)
// .orElseThrow(() -> new RuntimeException("User not found"));
// PrivateKey privateKey = RSAKeyUtil.getPrivateKeyFromBytes(user.getPrivateKey());
//
// Cipher rsaCipher = Cipher.getInstance("RSA");
// rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
// byte[] aesKeyBytes = rsaCipher.doFinal(encryptedAesKey);
//
// // Ensure valid AES key length
// if (aesKeyBytes.length != 16 && aesKeyBytes.length != 24 && aesKeyBytes.length != 32) {
// throw new RuntimeException("Invalid AES key length: " + aesKeyBytes.length + " bytes");
// }
//
// SecretKey aesKey = new SecretKeySpec(aesKeyBytes, 0, aesKeyBytes.length, "AES");
//
// // Read the encrypted file content
// byte[] encryptedFileContent = Files.readAllBytes(Paths.get(encFilePath));
//
// // Decrypt the file content using AES
// Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // Specify padding
// aesCipher.init(Cipher.DECRYPT_MODE, aesKey);
// byte[] decryptedFileContent = aesCipher.doFinal(encryptedFileContent);
//
// // Write the decrypted content to the original file
// Files.write(Paths.get(fullDecryptedPath + "." + fileExtension), decryptedFileContent);
//
// // Cleanup temporary files
// Files.deleteIfExists(Paths.get(encFilePath));
// Files.deleteIfExists(Paths.get(keyFilePath));
//
// } catch (Exception e) {
// throw new RuntimeException("Failed to download or decrypt file: " + e.getMessage(), e);
// }
// }
// Create an InputStream from the byte array
ByteArrayInputStream inputStream = new ByteArrayInputStream(fileData);
// Prepare the path for HDFS
String finalHdfsPath = hdfsPath.endsWith("/") ? hdfsPath + uploadedFileName : hdfsPath + "/" + uploadedFileName;
// Upload the file directly to HDFS from the InputStream
Path hdfsFilePath = new Path(finalHdfsPath);
FSDataOutputStream outputStream = fs.create(hdfsFilePath);
IOUtils.copyBytes(inputStream, outputStream, 4096, true);
} catch (IOException e) {
// Handle I/O exception and log the error
throw new RuntimeException("Failed to upload file to HDFS: " + e.getMessage(), e);
} catch (Exception e) {
// Catch any other exceptions
throw new RuntimeException("Failed to upload file to HDFS: " + e.getMessage(), e);
}
}
public void downloadFile(String hdfsEncPath, String localPathWithoutExt, String username) {
try {
FileSystem fs = HDFSConfig.getHDFS();
// Extract file name and extension
String encFileName = new File(hdfsEncPath).getName();
String originalFileName = encFileName.replace(".enc", "");
String fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".") + 1);
String fullDecryptedPath = localPathWithoutExt + "/" + originalFileName;
String encFilePath = fullDecryptedPath + ".enc";
String keyFilePath = fullDecryptedPath + ".key";
// Download encrypted file and AES key from HDFS
fs.copyToLocalFile(new Path(hdfsEncPath), new Path(encFilePath));
fs.copyToLocalFile(new Path(hdfsEncPath.replace(".enc", ".key")), new Path(keyFilePath));
// Read the encrypted AES key
byte[] encryptedAesKey = Files.readAllBytes(Paths.get(keyFilePath));
System.out.println("Length of encrypted AES key: " + encryptedAesKey.length);
// Retrieve the RSA private key for the user
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User not found"));
PrivateKey privateKey = RSAKeyUtil.getPrivateKeyFromBytes(user.getPrivateKey());
Cipher rsaCipher = Cipher.getInstance("RSA");
rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] aesKeyBytes = rsaCipher.doFinal(encryptedAesKey);
// Ensure valid AES key length
if (aesKeyBytes.length != 16 && aesKeyBytes.length != 24 && aesKeyBytes.length != 32) {
throw new RuntimeException("Invalid AES key length: " + aesKeyBytes.length + " bytes");
}
SecretKey aesKey = new SecretKeySpec(aesKeyBytes, 0, aesKeyBytes.length, "AES");
// Read the encrypted file content
byte[] encryptedFileContent = Files.readAllBytes(Paths.get(encFilePath));
// Decrypt the file content using AES
Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // Specify padding
aesCipher.init(Cipher.DECRYPT_MODE, aesKey);
byte[] decryptedFileContent = aesCipher.doFinal(encryptedFileContent);
// Write the decrypted content to the original file
Files.write(Paths.get(fullDecryptedPath + "." + fileExtension), decryptedFileContent);
// Cleanup temporary files
Files.deleteIfExists(Paths.get(encFilePath));
Files.deleteIfExists(Paths.get(keyFilePath));
} catch (Exception e) {
throw new RuntimeException("Failed to download or decrypt file: " + e.getMessage(), e);
}
}
// public void uploadFile(byte[] fileData, String hdfsPath, String uploadedFileName, String username) {
// try {
// FileSystem fs = HDFSConfig.getHDFS();
// ByteArrayInputStream inputStream = new ByteArrayInputStream(fileData);
// String finalHdfsPath = hdfsPath.endsWith("/") ? hdfsPath + uploadedFileName : hdfsPath + "/" + uploadedFileName;
// Path hdfsFilePath = new Path(finalHdfsPath);
// try (FSDataOutputStream outputStream = fs.create(hdfsFilePath)) {
// IOUtils.copyBytes(inputStream, outputStream, 4096, true);
// }
// } catch (IOException e) {
// throw new RuntimeException("Failed to upload file to HDFS: " + e.getMessage(), e);
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
// }
//
// public void downloadFile(String hdfsEncPath, String localPathWithoutExt, String username) {
// try {
// FileSystem fs = HDFSConfig.getHDFS();
// String encFilePath = localPathWithoutExt + ".enc";
// fs.copyToLocalFile(new Path(hdfsEncPath), new Path(encFilePath));
//
// User user = userRepository.findByUsername(username)
// .orElseThrow(() -> new RuntimeException("User not found"));
// PrivateKey privateKey = RSAKeyUtil.getPrivateKeyFromBytes(user.getPrivateKey());
//
// byte[] encryptedFileContent = Files.readAllBytes(Paths.get(encFilePath));
// byte[] decryptedFileContent = RSAKeyUtil.decrypt(encryptedFileContent, privateKey);
//
// Files.write(Paths.get(localPathWithoutExt), decryptedFileContent);
// Files.deleteIfExists(Paths.get(encFilePath));
// } catch (Exception e) {
// throw new RuntimeException("Failed to download or decrypt file: " + e.getMessage(), e);
// }
// }
public void createFolder(String hdfsPath) {
try {
@@ -1,83 +1,69 @@
package com.skycrate.backend.skycrateBackend.services;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import com.skycrate.backend.skycrateBackend.entity.User;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;
import java.util.function.Function;
@Service
public class JwtService {
@Value("${security.jwt.secret-key}")
private String secretKey;
@Value("${security.jwt.expiration-time}")
private long jwtExpiration;
private long expirationTime;
public String extractUsername(String token){
return extractClaim(token,Claims::getSubject);
private static final String SECRET_KEY = "PPp27xSTfBwOpRn4/AV6gPzQSnQg+Oi80KdWfCcuAHs=";
private Key getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(SECRET_KEY);
return Keys.hmacShaKeyFor(keyBytes);
}
public <T> T extractClaim(String token,Function<Claims,T> claimsResolver){
final Claims claims=extractAllClaims(token);
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
return claimsResolver.apply(claims);
}
public String generateToken(UserDetails userDetails) {
return generateToken(new HashMap<>(), userDetails);
}
public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
return buildToken(extraClaims, userDetails, jwtExpiration);
}
public long getExpirtationTime(){
return jwtExpiration;
}
private String buildToken(Map<String,Object> extraClaims,UserDetails userDetails,long expiration){
return Jwts.builder().setClaims(extraClaims).setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSignInKey(), SignatureAlgorithm.HS256)
.compact();
}
public boolean isTokenValid(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername())) && !isTokenExpired(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
private boolean isTokenExpired(String token) {
public boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
public String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expirationTime))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
private Claims extractAllClaims(String token) {
return Jwts
.parserBuilder()
.setSigningKey(getSignInKey())
.build()
.parseClaimsJws(token)
.getBody();
}
private Key getSignInKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
public String generateToken(User user) {
return generateToken((UserDetails) user);
}
}
@@ -0,0 +1,28 @@
package com.skycrate.backend.skycrateBackend.services;
import org.springframework.stereotype.Service;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class KeyCacheService {
private final ConcurrentHashMap<Long, String> keyCache = new ConcurrentHashMap<>();
public void cacheKey(Long userId, String decryptedKey) {
keyCache.put(userId, decryptedKey);
}
public String getKey(Long userId) {
return keyCache.get(userId);
}
public void clearKey(Long userId) {
keyCache.remove(userId);
}
public void clearAllKeys() {
keyCache.clear();
}
}
@@ -0,0 +1,23 @@
// NEED TO IMPLEMENT SAHI SE
package com.skycrate.backend.skycrateBackend.services;
import org.springframework.stereotype.Service;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class RateLimiterService {
private final ConcurrentHashMap<String, Integer> attempts = new ConcurrentHashMap<>();
public boolean isBlocked(String ip) {
return attempts.getOrDefault(ip, 0) >= 5;
}
public void recordFailedAttempt(String ip) {
attempts.put(ip, attempts.getOrDefault(ip, 0) + 1);
}
public void resetAttempts(String ip) {
attempts.remove(ip);
}
}
@@ -0,0 +1,65 @@
package com.skycrate.backend.skycrateBackend.services;
import com.skycrate.backend.skycrateBackend.entity.RefreshToken;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.RefreshTokenRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.util.Optional;
import java.util.UUID;
@Service
public class RefreshTokenService {
private final RefreshTokenRepository refreshTokenRepo;
@Value("${security.jwt.refresh-expiry-ms:86400000}") //1 day in milliseconds
private Long refreshTokenDurationMs;
public RefreshTokenService(RefreshTokenRepository refreshTokenRepo) {
this.refreshTokenRepo = refreshTokenRepo;
}
@Transactional
public RefreshToken createRefreshToken(User user) {
refreshTokenRepo.deleteByUser(user);
refreshTokenRepo.flush();
RefreshToken token = new RefreshToken();
token.setUser(user);
token.setExpiryDate(Instant.now().plusMillis(refreshTokenDurationMs));
token.setToken(UUID.randomUUID().toString());
return refreshTokenRepo.save(token);
}
public Optional<RefreshToken> findByToken(String token) {
return refreshTokenRepo.findByToken(token);
}
public boolean isExpired(RefreshToken token) {
return token.getExpiryDate().isBefore(Instant.now());
}
@Transactional
public void deleteByUser(User user) {
try {
refreshTokenRepo.deleteByUser(user);
System.out.println("Successfully deleted refresh tokens for user: " + user.getId());
} catch (Exception e) {
System.err.println("Error deleting refresh tokens for user: " + user.getId() + " - " + e.getMessage());
}
}
@Transactional
public void logout(User user) {
deleteByUser(user); // This should call the repository method to delete the token
}
public Optional<RefreshToken> refreshAccessToken(String refreshToken) {
return findByToken(refreshToken).filter(token -> !isExpired(token));
}
}
@@ -0,0 +1,52 @@
package com.skycrate.backend.skycrateBackend.services;
import com.skycrate.backend.skycrateBackend.dto.SignupRequest;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.math.BigInteger;
import java.security.MessageDigest;
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
public void registerUser(SignupRequest request) {
if (isPasswordPwned(request.getPassword())) {
throw new IllegalArgumentException("Password has been compromised in data breaches.");
}
User user = new User();
user.setUsername(request.getUsername());
user.setEmail(request.getEmail());
user.setPassword(passwordEncoder.encode(request.getPassword()));
userRepository.save(user);
}
private boolean isPasswordPwned(String password) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] hash = md.digest(password.getBytes());
String fullHash = String.format("%040x", new BigInteger(1, hash)).toUpperCase();
String prefix = fullHash.substring(0, 5);
String suffix = fullHash.substring(5);
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject("https://api.pwnedpasswords.com/range/" + prefix, String.class);
return response != null && response.contains(suffix);
} catch (Exception e) {
return false; // If API fails, allow but log in production
}
}
}
@@ -0,0 +1,94 @@
package com.skycrate.backend.skycrateBackend.utils;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.*;
import java.security.*;
import javax.crypto.spec.PBEKeySpec;
import java.util.Arrays;
public class EncryptionUtil {
private static final int SALT_LENGTH = 16;
private static final int IV_LENGTH = 16;
private static final int ITERATIONS = 65536;
private static final int KEY_LENGTH = 256;
private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
private static final String AES_CIPHER = "AES/CBC/PKCS5Padding";
private static final String RSA_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
private static final SecureRandom secureRandom = new SecureRandom();
// ------------------------ AES ------------------------
public static byte[] generateSalt() {
byte[] salt = new byte[SALT_LENGTH];
secureRandom.nextBytes(salt);
return salt;
}
public static byte[] generateIv() {
byte[] iv = new byte[IV_LENGTH];
secureRandom.nextBytes(iv);
return iv;
}
public static SecretKey deriveKey(char[] password, byte[] salt) throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
SecretKey tmp = factory.generateSecret(spec);
return new SecretKeySpec(tmp.getEncoded(), "AES");
}
public static SecretKey generateAESKey() throws Exception {
byte[] keyBytes = new byte[KEY_LENGTH / 8]; // 256 bits
secureRandom.nextBytes(keyBytes);
return new SecretKeySpec(keyBytes, "AES");
}
public static SecretKey rebuildAESKey(byte[] keyBytes) {
return new SecretKeySpec(keyBytes, "AES");
}
public static byte[] encrypt(byte[] plaintext, SecretKey key, byte[] iv) throws Exception {
Cipher cipher = Cipher.getInstance(AES_CIPHER);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
return cipher.doFinal(plaintext);
}
public static byte[] decrypt(byte[] ciphertext, SecretKey key, byte[] iv) throws Exception {
Cipher cipher = Cipher.getInstance(AES_CIPHER);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
return cipher.doFinal(ciphertext);
}
// ------------------------ RSA ------------------------
public static byte[] encryptRSA(byte[] data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance(RSA_CIPHER);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
public static byte[] decryptRSA(byte[] data, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance(RSA_CIPHER);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
// // --------- Encrypt/decrypt RSA private key using AES derived from password ---------
//
// public static byte[] encryptPrivateKey(PrivateKey privateKey, String password, byte[] salt, byte[] iv) throws Exception {
// SecretKey aesKey = deriveKey(password.toCharArray(), salt);
// return encrypt(privateKey.getEncoded(), aesKey, iv);
// }
//
// public static byte[] decryptPrivateKey(byte[] encryptedPrivateKey, String password, byte[] salt, byte[] iv) throws Exception {
// SecretKey aesKey = deriveKey(password.toCharArray(), salt);
// return decrypt(encryptedPrivateKey, aesKey, iv);
// }
}
@@ -9,31 +9,31 @@ import java.security.spec.X509EncodedKeySpec;
public class KeyUtil {
public static void generateAndStoreKeyPair(String username) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048); // Key size
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// Store the public key
Path publicKeyPath = Paths.get("C:\\Users\\sonal\\OneDrive\\Desktop\\SkyCrate\\Skycrate\\keys", username + "_public.key");
Files.write(publicKeyPath, keyPair.getPublic().getEncoded());
// Store the private key
Path privateKeyPath = Paths.get("C:\\Users\\sonal\\OneDrive\\Desktop\\SkyCrate\\Skycrate\\keys", username + "_private.key");
Files.write(privateKeyPath, keyPair.getPrivate().getEncoded());
}
public static PublicKey getPublicKeyForUser(String username) throws Exception {
Path path = Paths.get("C:\\Users\\sonal\\OneDrive\\Desktop\\SkyCrate\\Skycrate\\keys", username + "_public.key");
byte[] bytes = Files.readAllBytes(path);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
return KeyFactory.getInstance("RSA").generatePublic(keySpec);
}
public static PrivateKey getPrivateKeyForUser(String username) throws Exception {
Path path = Paths.get("C:\\Users\\sonal\\OneDrive\\Desktop\\SkyCrate\\Skycrate\\keys", username + "_private.key");
byte[] bytes = Files.readAllBytes(path);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
return KeyFactory.getInstance("RSA").generatePrivate(keySpec);
}
// public static void generateAndStoreKeyPair(String username) throws Exception {
// KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
// keyPairGenerator.initialize(2048); // Key size
// KeyPair keyPair = keyPairGenerator.generateKeyPair();
//
// // Store the public key
// Path publicKeyPath = Paths.get("C:\\Users\\sonal\\OneDrive\\Desktop\\SkyCrate\\Skycrate\\keys", username + "_public.key");
// Files.write(publicKeyPath, keyPair.getPublic().getEncoded());
//
// // Store the private key
// Path privateKeyPath = Paths.get("C:\\Users\\sonal\\OneDrive\\Desktop\\SkyCrate\\Skycrate\\keys", username + "_private.key");
// Files.write(privateKeyPath, keyPair.getPrivate().getEncoded());
// }
//
// public static PublicKey getPublicKeyForUser(String username) throws Exception {
// Path path = Paths.get("C:\\Users\\sonal\\OneDrive\\Desktop\\SkyCrate\\Skycrate\\keys", username + "_public.key");
// byte[] bytes = Files.readAllBytes(path);
// X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
// return KeyFactory.getInstance("RSA").generatePublic(keySpec);
// }
//
// public static PrivateKey getPrivateKeyForUser(String username) throws Exception {
// Path path = Paths.get("C:\\Users\\sonal\\OneDrive\\Desktop\\SkyCrate\\Skycrate\\keys", username + "_private.key");
// byte[] bytes = Files.readAllBytes(path);
// PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
// return KeyFactory.getInstance("RSA").generatePrivate(keySpec);
// }
}
@@ -29,42 +29,45 @@ public class RSAKeyUtil {
return keyFactory.generatePrivate(spec);
}
// Shorthand decode aliases
public static PublicKey decodePublicKey(byte[] publicKeyBytes) throws Exception {
return getPublicKeyFromBytes(publicKeyBytes);
}
public static PrivateKey decodePrivateKey(byte[] privateKeyBytes) throws Exception {
return getPrivateKeyFromBytes(privateKeyBytes);
}
// Encrypt data using RSA (with padding)
public static byte[] encrypt(byte[] data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); // Specify padding
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
// Decrypt data using RSA (with padding)
public static byte[] decrypt(byte[] encryptedData, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); // Specify padding
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(encryptedData);
}
// Generate AES Key (128, 192, or 256 bits)
public static SecretKey generateAESKey(int keySize) throws NoSuchAlgorithmException {
if (keySize != 128 && keySize != 192 && keySize != 256) {
throw new IllegalArgumentException("Invalid AES key size. Must be 128, 192, or 256 bits.");
}
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(keySize); // Specify the key size
return keyGenerator.generateKey();
}
// Encrypt AES Key using RSA
public static byte[] encryptAESKey(SecretKey aesKey, PublicKey publicKey) throws Exception {
return encrypt(aesKey.getEncoded(), publicKey); // Encrypt the AES key using RSA
}
// Decrypt AES Key using RSA
public static SecretKey decryptAESKey(byte[] encryptedAESKey, PrivateKey privateKey, int keySize) throws Exception {
byte[] decryptedKey = decrypt(encryptedAESKey, privateKey); // Decrypt with RSA
// Ensure that the decrypted key length matches the expected AES key size
if (decryptedKey.length != keySize / 8) {
throw new IllegalArgumentException("Decrypted key size does not match expected AES key size.");
}
return new SecretKeySpec(decryptedKey, 0, decryptedKey.length, "AES"); // Convert to AES Key
}
// // AES key generation
// public static SecretKey generateAESKey(int keySize) throws NoSuchAlgorithmException {
// if (keySize != 128 && keySize != 192 && keySize != 256) {
// throw new IllegalArgumentException("Invalid AES key size. Must be 128, 192, or 256 bits.");
// }
// KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
// keyGenerator.init(keySize);
// return keyGenerator.generateKey();
// }
//
// public static byte[] encryptAESKey(SecretKey aesKey, PublicKey publicKey) throws Exception {
// return encrypt(aesKey.getEncoded(), publicKey);
// }
//
// public static SecretKey decryptAESKey(byte[] encryptedAESKey, PrivateKey privateKey, int keySize) throws Exception {
// byte[] decryptedKey = decrypt(encryptedAESKey, privateKey);
// return new SecretKeySpec(decryptedKey, 0, decryptedKey.length, "AES");
// }
}
+8
View File
@@ -0,0 +1,8 @@
server:
port: 8443
ssl:
enabled: true
key-store: classpath:keystore.p12
key-store-password: changeit
key-store-type: PKCS12
key-alias: tomcat
+17 -23
View File
@@ -5,13 +5,13 @@ spring.servlet.multipart.max-request-size=1000MB
spring.servlet.multipart.enabled=true
security.jwt.secret-key=3cfa76ef14937c1c0ea519f8fc057a80fcd04a7420f8e8bcd0a7567c272e007b
security.jwt.secret-key=${JWT_SECRET}
security.jwt.expiration-time=3600000
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
spring.datasource.username=kshitij
spring.datasource.password=loa_dngLLA8729
spring.datasource.url=jdbc:mysql://192.168.29.55:3306/skycrate
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
spring.datasource.url=jdbc:mysql://${DB_URI}/${DB_NAME}
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
@@ -21,24 +21,18 @@ logging.level.org.springframework.security.config.annotation.authentication.conf
server.port=8080
server.ssl.enabled=true
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=${SSL_PASSWORD}
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=skycrateSSL
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.endpoints.enabled-by-default=true
# spring.application.name=skycrateBackend
# spring.servlet.multipart.max-file-size=1000MB
# spring.servlet.multipart.max-request-size=1000MB
# security.jwt.secret-key=3cfa76ef14937c1c0ea519f8fc057a80fcd04a7420f8e8bcd0a7567c272e007b
# security.jwt.expiration-time=3600000
# spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
# spring.datasource.username=root
# spring.datasource.password=sqlsys
# spring.datasource.url=jdbc:mysql://localhost:3306/skycrate
# spring.jpa.hibernate.ddl-auto=update
# spring.jpa.show-sql=true
# spring.jpa.properties.hibernate.format_sql=true
# server.port=8081
# Allow unauthenticated access
#management.server.port=8080
#management.server.ssl.enabled=false
#management.endpoints.web.base-path=/actuator
#management.endpoints.web.exposure.include=*
+1
View File
@@ -0,0 +1 @@
GENERATE USING: keytool -genkeypair -alias skycrateSSL -keyalg RSA -keysize 4096 -keystore keystore.p12 -storetype PKCS12 -validity 3650 -dname "CN=localhost, OU=Skycrate, O=Skycrate, C=India"