-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathAuthenticationHandler.java
More file actions
212 lines (179 loc) · 6.91 KB
/
AuthenticationHandler.java
File metadata and controls
212 lines (179 loc) · 6.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
package org.openmbee.flexo.cli.client;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileReader;
import java.io.IOException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Security;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
/**
* Handles SSH key-based JWT authentication for Flexo MMS
* Also supports local mode with hardcoded user and HMAC-based JWT
*/
public class AuthenticationHandler {
private static final Logger logger = LoggerFactory.getLogger(AuthenticationHandler.class);
static {
// Add BouncyCastle as security provider
Security.addProvider(new BouncyCastleProvider());
}
private final boolean enabled;
private final String sshKeyPath;
private final boolean localMode;
private final String localUser;
private final String localJwtSecret;
private PrivateKey privateKey;
private String cachedToken;
private Instant tokenExpiry;
public AuthenticationHandler(boolean enabled, String sshKeyPath) {
this(enabled, sshKeyPath, false, null, null);
}
public AuthenticationHandler(boolean enabled, String sshKeyPath, boolean localMode, String localUser, String localJwtSecret) {
this.enabled = enabled;
this.sshKeyPath = sshKeyPath;
this.localMode = localMode;
this.localUser = localUser;
this.localJwtSecret = localJwtSecret;
// Validate JWT secret in local mode
if (localMode) {
validateJwtSecret(localJwtSecret);
}
if (enabled && !localMode) {
loadPrivateKey();
}
}
/**
* Validate JWT secret for security
* Skip warnings for secrets generated by this application (they're already secure)
*/
private void validateJwtSecret(String secret) {
if (secret == null || secret.isEmpty()) {
logger.error("SECURITY WARNING: local.jwtSecret is not configured. JWT authentication will fail.");
logger.error("Please set local.jwtSecret in ~/.flexo/config");
return;
}
if (secret.length() < 32) {
logger.warn("SECURITY WARNING: local.jwtSecret is too short (minimum 32 characters recommended)");
}
}
/**
* Load SSH private key from file
*/
private void loadPrivateKey() {
if (sshKeyPath == null || sshKeyPath.isEmpty()) {
logger.warn("SSH key path not configured");
return;
}
try (PEMParser pemParser = new PEMParser(new FileReader(sshKeyPath))) {
Object object = pemParser.readObject();
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
if (object instanceof PEMKeyPair) {
KeyPair keyPair = converter.getKeyPair((PEMKeyPair) object);
this.privateKey = keyPair.getPrivate();
logger.info("Loaded SSH private key from: {}", sshKeyPath);
} else if (object instanceof KeyPair) {
this.privateKey = ((KeyPair) object).getPrivate();
logger.info("Loaded SSH private key from: {}", sshKeyPath);
} else {
logger.error("Unsupported key format in file: {}", sshKeyPath);
}
} catch (IOException e) {
logger.error("Failed to load SSH private key: {}", e.getMessage());
}
}
/**
* Get JWT token for authentication
* Returns cached token if still valid, otherwise generates new one
*/
public String getToken() {
if (!enabled && !localMode) {
return null;
}
// Return cached token if still valid
if (cachedToken != null && tokenExpiry != null && Instant.now().isBefore(tokenExpiry)) {
return cachedToken;
}
// Generate new token (local mode or SSH key mode)
return generateToken();
}
/**
* Generate a new JWT token signed with SSH private key or HMAC secret
*/
private String generateToken() {
try {
Instant now = Instant.now();
Instant expiry = now.plus(1, ChronoUnit.HOURS);
String token;
if (localMode) {
// Local mode: generate HMAC-based JWT with hardcoded user
if (localJwtSecret == null || localJwtSecret.isEmpty()) {
logger.error("Cannot generate token: local JWT secret not configured");
return null;
}
String username = localUser != null ? localUser : "root";
token = Jwts.builder()
.issuer("http://localhost:8080/")
.audience().add("flexo-mms").and()
.issuedAt(Date.from(now))
.expiration(Date.from(expiry))
.claim("username", username)
.claim("groups", new java.util.ArrayList<>())
.signWith(Keys.hmacShaKeyFor(localJwtSecret.getBytes()))
.compact();
logger.debug("Generated local mode JWT token for user '{}', expires at: {}", username, expiry);
} else {
// SSH key mode: generate RSA-based JWT
if (privateKey == null) {
logger.error("Cannot generate token: private key not loaded");
return null;
}
token = Jwts.builder()
.subject("flexo-cli-user")
.issuedAt(Date.from(now))
.expiration(Date.from(expiry))
.claim("scope", "flexo:mms")
.signWith(privateKey)
.compact();
logger.debug("Generated SSH key JWT token, expires at: {}", expiry);
}
// Cache token
this.cachedToken = token;
this.tokenExpiry = expiry;
return token;
} catch (Exception e) {
logger.error("Failed to generate JWT token: {}", e.getMessage());
return null;
}
}
/**
* Get Authorization header value
*/
public String getAuthorizationHeader() {
if (!enabled && !localMode) {
return null;
}
String token = getToken();
if (token == null) {
return null;
}
return "Bearer " + token;
}
/**
* Clear cached token (force regeneration on next request)
*/
public void clearCache() {
this.cachedToken = null;
this.tokenExpiry = null;
}
public boolean isEnabled() {
return enabled || localMode;
}
}