This is an automated email from the ASF dual-hosted git repository.

lahirujayathilake pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git


The following commit(s) were added to refs/heads/master by this push:
     new b5df7d215 changed the token signing implementation to store/fetch key 
pairs from vault
b5df7d215 is described below

commit b5df7d215a1240cc4388dd3b40679290cbe9f506
Author: lahiruj <[email protected]>
AuthorDate: Mon Dec 2 19:23:15 2024 -0500

    changed the token signing implementation to store/fetch key pairs from vault
---
 .../api/identity/IdentityManagementController.java |  12 +-
 application/src/main/resources/application.yml     |   2 -
 application/src/main/resources/static/favicon.ico  | Bin 0 -> 3084 bytes
 .../org/apache/custos/service/auth/KeyLoader.java  |  96 ----------------
 .../org/apache/custos/service/auth/KeyService.java | 121 +++++++++++++++++++++
 .../apache/custos/service/auth/TokenService.java   |  10 +-
 .../credential/store/CredentialStoreService.java   |  24 ++++
 7 files changed, 156 insertions(+), 109 deletions(-)

diff --git 
a/api/src/main/java/org/apache/custos/api/identity/IdentityManagementController.java
 
b/api/src/main/java/org/apache/custos/api/identity/IdentityManagementController.java
index 487401d39..12f1b9d34 100644
--- 
a/api/src/main/java/org/apache/custos/api/identity/IdentityManagementController.java
+++ 
b/api/src/main/java/org/apache/custos/api/identity/IdentityManagementController.java
@@ -39,7 +39,7 @@ import 
org.apache.custos.core.identity.management.api.AuthorizationResponse;
 import org.apache.custos.core.identity.management.api.EndSessionRequest;
 import org.apache.custos.core.identity.management.api.GetCredentialsRequest;
 import org.apache.custos.service.auth.AuthClaim;
-import org.apache.custos.service.auth.KeyLoader;
+import org.apache.custos.service.auth.KeyService;
 import org.apache.custos.service.auth.TokenAuthorizer;
 import org.apache.custos.service.credential.store.Credential;
 import org.apache.custos.service.credential.store.CredentialManager;
@@ -84,12 +84,12 @@ public class IdentityManagementController {
 
     private final IdentityManagementService identityManagementService;
     private final TokenAuthorizer tokenAuthorizer;
-    private final KeyLoader keyLoader;
+    private final KeyService keyService;
 
-    public IdentityManagementController(IdentityManagementService 
identityManagementService, TokenAuthorizer tokenAuthorizer, KeyLoader 
keyLoader) {
+    public IdentityManagementController(IdentityManagementService 
identityManagementService, TokenAuthorizer tokenAuthorizer, KeyService 
keyService) {
         this.identityManagementService = identityManagementService;
         this.tokenAuthorizer = tokenAuthorizer;
-        this.keyLoader = keyLoader;
+        this.keyService = keyService;
     }
 
     @PostMapping("/authenticate")
@@ -529,11 +529,11 @@ public class IdentityManagementController {
 
     @GetMapping("/.well-known/jwks.json")
     public ResponseEntity<?> keys() {
-        KeyPair keyPair = keyLoader.getKeyPair();
+        KeyPair keyPair = keyService.getKeyPair();
         RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
 
         JWK jwk = new RSAKey.Builder(rsaPublicKey)
-                .keyID(keyLoader.getKeyID())
+                .keyID(keyService.getKeyID())
                 .keyUse(KeyUse.SIGNATURE)
                 .build();
 
diff --git a/application/src/main/resources/application.yml 
b/application/src/main/resources/application.yml
index 506630738..f3e563e29 100644
--- a/application/src/main/resources/application.yml
+++ b/application/src/main/resources/application.yml
@@ -29,8 +29,6 @@ custos:
   tenant:
     base:
       uri: http://localhost/api/v1/tenant-management/oauth2/tenant
-  keys:
-    location:<CHANGE_PATH>/application/src/main/resources/keys
   api:
     domain: "http://localhost:8081";
 
diff --git a/application/src/main/resources/static/favicon.ico 
b/application/src/main/resources/static/favicon.ico
new file mode 100644
index 000000000..c56418262
Binary files /dev/null and b/application/src/main/resources/static/favicon.ico 
differ
diff --git 
a/services/src/main/java/org/apache/custos/service/auth/KeyLoader.java 
b/services/src/main/java/org/apache/custos/service/auth/KeyLoader.java
deleted file mode 100644
index c734373ab..000000000
--- a/services/src/main/java/org/apache/custos/service/auth/KeyLoader.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-
-package org.apache.custos.service.auth;
-
-import jakarta.annotation.PostConstruct;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Component;
-
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.MessageDigest;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.Base64;
-
-@Component
-public class KeyLoader {
-
-    private KeyPair keyPair;
-    private String keyID;
-
-    @Value("${custos.keys.location}")
-    private String keysLocation;
-
-    @PostConstruct
-    public void init() {
-        try {
-            this.keyPair = loadKeyPair();
-            this.keyID = computeKeyID(keyPair.getPublic());
-        } catch (Exception e) {
-            throw new RuntimeException("Failed to load key pair", e);
-        }
-    }
-
-    public KeyPair getKeyPair() {
-        return this.keyPair;
-    }
-
-    public String getKeyID() {
-        return this.keyID;
-    }
-
-    private KeyPair loadKeyPair() throws Exception {
-        System.out.println("Key location - " + keysLocation);
-        String privateKeyContent = new 
String(Files.readAllBytes(Paths.get(keysLocation + "/private_key.pem")));
-        String publicKeyContent = new 
String(Files.readAllBytes(Paths.get(keysLocation + "/public_key.pem")));
-
-        privateKeyContent = privateKeyContent.replaceAll("-----BEGIN PRIVATE 
KEY-----", "")
-                .replaceAll("-----END PRIVATE KEY-----", "")
-                .replaceAll("\\s+", "");
-
-        publicKeyContent = publicKeyContent.replaceAll("-----BEGIN PUBLIC 
KEY-----", "")
-                .replaceAll("-----END PUBLIC KEY-----", "")
-                .replaceAll("\\s+", "");
-
-        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
-
-        byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyContent);
-        byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyContent);
-
-        PKCS8EncodedKeySpec privateKeySpec = new 
PKCS8EncodedKeySpec(privateKeyBytes);
-        X509EncodedKeySpec publicKeySpec = new 
X509EncodedKeySpec(publicKeyBytes);
-
-        PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
-        PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
-
-        return new KeyPair(publicKey, privateKey);
-    }
-
-    private String computeKeyID(PublicKey publicKey) throws Exception {
-        MessageDigest digest = MessageDigest.getInstance("SHA-256");
-        byte[] hash = digest.digest(publicKey.getEncoded());
-        return Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
-    }
-}
diff --git 
a/services/src/main/java/org/apache/custos/service/auth/KeyService.java 
b/services/src/main/java/org/apache/custos/service/auth/KeyService.java
new file mode 100644
index 000000000..a64fef285
--- /dev/null
+++ b/services/src/main/java/org/apache/custos/service/auth/KeyService.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.custos.service.auth;
+
+import org.apache.custos.service.credential.store.CredentialStoreService;
+import org.springframework.stereotype.Service;
+
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.MessageDigest;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+import java.util.Map;
+
+@Service
+public class KeyService {
+
+    private final CredentialStoreService credentialStoreService;
+
+    private volatile KeyPair keyPair;
+    private volatile String keyID;
+
+    public KeyService(CredentialStoreService credentialStoreService) {
+        this.credentialStoreService = credentialStoreService;
+    }
+
+    public KeyPair getKeyPair() {
+        if (this.keyPair == null) {
+            synchronized (this) {
+                if (this.keyPair == null) {
+                    loadOrGenerateKeyPair();
+                }
+            }
+        }
+        return this.keyPair;
+    }
+
+    public String getKeyID() {
+        if (this.keyID == null) {
+            synchronized (this) {
+                if (this.keyID == null) {
+                    this.keyID = computeKeyID(getKeyPair().getPublic());
+                }
+            }
+        }
+        return this.keyID;
+    }
+
+    private void loadOrGenerateKeyPair() {
+        try {
+            Map<String, String> keyData = 
credentialStoreService.retrieveKeyPair();
+            if (keyData == null || keyData.isEmpty()) {
+                generateAndStoreKeyPair();
+            } else {
+                this.keyPair = createKeyPairFromData(keyData);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to load or generate key pair", 
e);
+        }
+    }
+
+    private void generateAndStoreKeyPair() {
+        try {
+            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+            keyGen.initialize(2048);
+            KeyPair generatedKeyPair = keyGen.generateKeyPair();
+
+            String privateKey = 
Base64.getEncoder().encodeToString(generatedKeyPair.getPrivate().getEncoded());
+            String publicKey = 
Base64.getEncoder().encodeToString(generatedKeyPair.getPublic().getEncoded());
+
+            credentialStoreService.storeKeyPair(privateKey, publicKey);
+
+            this.keyPair = generatedKeyPair;
+            this.keyID = computeKeyID(generatedKeyPair.getPublic());
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to generate and store key 
pair", e);
+        }
+    }
+
+    private KeyPair createKeyPairFromData(Map<String, String> keyData) throws 
Exception {
+        byte[] privateKeyBytes = 
Base64.getDecoder().decode(keyData.get("privateKey"));
+        byte[] publicKeyBytes = 
Base64.getDecoder().decode(keyData.get("publicKey"));
+
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PrivateKey privateKey = keyFactory.generatePrivate(new 
PKCS8EncodedKeySpec(privateKeyBytes));
+        PublicKey publicKey = keyFactory.generatePublic(new 
X509EncodedKeySpec(publicKeyBytes));
+
+        return new KeyPair(publicKey, privateKey);
+    }
+
+    private String computeKeyID(PublicKey publicKey) {
+        try {
+            MessageDigest digest = MessageDigest.getInstance("SHA-256");
+            byte[] hash = digest.digest(publicKey.getEncoded());
+            return 
Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to compute key ID", e);
+        }
+    }
+}
diff --git 
a/services/src/main/java/org/apache/custos/service/auth/TokenService.java 
b/services/src/main/java/org/apache/custos/service/auth/TokenService.java
index d7644aa41..7124b898b 100644
--- a/services/src/main/java/org/apache/custos/service/auth/TokenService.java
+++ b/services/src/main/java/org/apache/custos/service/auth/TokenService.java
@@ -51,21 +51,21 @@ public class TokenService {
 
     private static final Logger LOGGER = 
LoggerFactory.getLogger(TokenService.class);
 
-    private final KeyLoader keyLoader;
+    private final KeyService keyService;
     private final UserProfileService userProfileService;
     private final CacheManager cacheManager;
 
     @Autowired
-    public TokenService(KeyLoader keyLoader, UserProfileService 
userProfileService, CacheManager cacheManager) {
-        this.keyLoader = keyLoader;
+    public TokenService(KeyService keyService, UserProfileService 
userProfileService, CacheManager cacheManager) {
+        this.keyService = keyService;
         this.userProfileService = userProfileService;
         this.cacheManager = cacheManager;
     }
 
 
     public String generateWithCustomClaims(String token, long tenantId) throws 
Exception {
-        KeyPair keyPair = keyLoader.getKeyPair();
-        String keyID = keyLoader.getKeyID();
+        KeyPair keyPair = keyService.getKeyPair();
+        String keyID = keyService.getKeyID();
 
         SignedJWT signedJWT = SignedJWT.parse(token);
 
diff --git 
a/services/src/main/java/org/apache/custos/service/credential/store/CredentialStoreService.java
 
b/services/src/main/java/org/apache/custos/service/credential/store/CredentialStoreService.java
index e080d9686..61b8f5ff9 100644
--- 
a/services/src/main/java/org/apache/custos/service/credential/store/CredentialStoreService.java
+++ 
b/services/src/main/java/org/apache/custos/service/credential/store/CredentialStoreService.java
@@ -44,10 +44,13 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
 import org.springframework.vault.core.VaultTemplate;
+import org.springframework.vault.support.VaultResponse;
 import org.springframework.vault.support.VaultResponseSupport;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 
 /**
@@ -60,6 +63,7 @@ public class CredentialStoreService {
     private static final Logger LOGGER = 
LoggerFactory.getLogger(CredentialStoreService.class);
 
     private static final String BASE_PATH = "/secret/";
+    private static final String KEY_PATH = BASE_PATH + "keys";
 
     private final VaultTemplate vaultTemplate;
 
@@ -597,6 +601,26 @@ public class CredentialStoreService {
         }
     }
 
+    public void storeKeyPair(String privateKey, String publicKey) {
+        Map<String, String> keys = new HashMap<>();
+        keys.put("privateKey", privateKey);
+        keys.put("publicKey", publicKey);
+
+        Map<String, Object> data = new HashMap<>();
+        data.put("data", keys);
+
+        vaultTemplate.write(KEY_PATH, data);
+    }
+
+    @SuppressWarnings("unchecked")
+    public Map<String, String> retrieveKeyPair() {
+        VaultResponse response = vaultTemplate.read(KEY_PATH);
+        if (response != null && response.getData() != null) {
+            return (Map<String, String>) response.getData().get("data");
+        }
+        return null;
+    }
+
     private OperationMetadata convertFromEntity(StatusEntity entity) {
         return OperationMetadata.newBuilder()
                 .setEvent(entity.getEvent())

Reply via email to