bug6095: added persistence for AI Core Credentials, using AES encryption with random salt

This commit is contained in:
Axel Uhl
2025-03-06 15:18:46 +01:00
parent 27a621b188
commit 671eb2cc47
14 changed files with 164 additions and 10 deletions
@@ -7,6 +7,7 @@ import java.net.MalformedURLException;
import org.json.simple.parser.ParseException;
import com.sap.sse.aicore.impl.CredentialsParserImpl;
import com.sap.sse.common.Util.Pair;
public interface CredentialsParser {
static CredentialsParser create() {
@@ -16,4 +17,17 @@ public interface CredentialsParser {
Credentials parse(Reader r) throws IOException, ParseException;
Credentials parse(CharSequence s) throws ParseException, MalformedURLException;
/**
* @return a pair of which the {@link Pair#getA() first} component represents the encoded credentials, using a
* random "salt" returned as the {@link Pair#getB() second} component of the pair returned. These can be
* used as the two arguments to {@link #parseFromEncoded(CharSequence)} to obtain {@link Credentials}
* equivalent to the {@code credentials} passed to this method again.
*/
Pair<String, String> getAsEncodedString(Credentials credentials);
/**
* The inverse for {@link #getAsEncodedString(Credentials)}
*/
Credentials parseFromEncoded(CharSequence encoded, String salt);
}
@@ -94,6 +94,18 @@ public class CredentialsImpl implements Credentials {
return token;
}
String getClientId() {
return clientId;
}
String getClientSecret() {
return clientSecret;
}
URL getXsuaaUrl() {
return xsuaaUrl;
}
String fetchToken() throws URISyntaxException, UnsupportedOperationException, ClientProtocolException, IOException, ParseException {
final HttpPost postRequest = new HttpPost(new URI(xsuaaUrl.toString() + CLIENT_CREDENTIALS_PATH));
postRequest.setHeader("Content-Type", "application/x-www-form-urlencoded");
@@ -3,6 +3,11 @@ package com.sap.sse.aicore.impl;
import java.io.IOException;
import java.io.Reader;
import java.net.MalformedURLException;
import java.util.Base64;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
@@ -10,6 +15,7 @@ import org.json.simple.parser.ParseException;
import com.sap.sse.aicore.Credentials;
import com.sap.sse.aicore.CredentialsParser;
import com.sap.sse.common.Util.Pair;
public class CredentialsParserImpl implements CredentialsParser {
private final static String CLIENT_ID = "clientid";
@@ -44,4 +50,60 @@ public class CredentialsParserImpl implements CredentialsParser {
final String aiApiUrl = (String) serviceURLs.get(AI_API_URL);
return new CredentialsImpl(clientId, clientSecret, url, identityZone, identityZoneId, appName, aiApiUrl);
}
@Override
public Pair<String, String> getAsEncodedString(Credentials credentials) {
final String clientId = ((CredentialsImpl) credentials).getClientId();
final String clientSecret = ((CredentialsImpl) credentials).getClientSecret();
final String url = ((CredentialsImpl) credentials).getXsuaaUrl().toString();
final String identityZone = credentials.getIdentityZone();
final String identityZoneId = credentials.getIdentityZoneId();
final String appName = credentials.getAppName();
final String aiApiUrl = credentials.getAiApiUrl().toString();
final JSONObject jsonCredentials = new JSONObject();
jsonCredentials.put(CLIENT_ID, clientId);
jsonCredentials.put(CLIENT_SECRET, clientSecret);
jsonCredentials.put(URL, url);
jsonCredentials.put(IDENTITY_ZONE, identityZone);
jsonCredentials.put(IDENTITY_ZONE_ID, identityZoneId);
jsonCredentials.put(APP_NAME, appName);
final JSONObject serviceURLs = new JSONObject();
jsonCredentials.put(SERVICE_URLS, serviceURLs);
serviceURLs.put(AI_API_URL, aiApiUrl);
final String salt = createRandomSalt();
SecretKeySpec secretKey = new SecretKeySpec(salt.getBytes(), "AES"); // 16-byte key for AES
try {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedBytes = cipher.doFinal(jsonCredentials.toJSONString().getBytes());
return new Pair<>(Base64.getEncoder().encodeToString(encryptedBytes), salt);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private String createRandomSalt() {
final Random random = new Random();
final int numberOfCharacters = 16;
final char[] chars = new char[numberOfCharacters];
for (int i=0; i<numberOfCharacters; i++) {
chars[i] = (char) (((int) 'A') + random.nextInt((int) 'z' - (int) 'A'));
}
return new String(chars);
}
@Override
public Credentials parseFromEncoded(CharSequence encoded, String salt) {
final byte[] decodedBytes = Base64.getDecoder().decode(encoded.toString().getBytes());
final SecretKeySpec secretKey = new SecretKeySpec(salt.getBytes(), "AES"); // 16-byte key for AES
try {
final Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
final byte[] decryptedBytes = cipher.doFinal(decodedBytes);
final String decryptedString = new String(decryptedBytes);
return parse(decryptedString);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}