-
Notifications
You must be signed in to change notification settings - Fork 5
feat: support self-managed key management via KeyProvider #56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@heng-gezhizou Thank you very much for submitting this PR — we truly appreciate the time, effort, and thought you’ve put into it. The idea and direction behind this change are very valuable and helpful for the project. We will review it carefully as soon as possible. During the review, we may choose to adopt the PR directly, or rework the implementation based on your proposed approach to better align with the overall architecture and long-term maintainability of the SDK. Either way, your contribution is highly appreciated, and it has provided important insights for improving the project. Thank you again for your support! |
|
@heng-gezhizou Thank you for the PR. After reviewing the code, there is one point we’d like to clarify with you. In our API communication model, the same RSA key pair is used to sign request data and to decrypt response data. However, based on our understanding, asymmetric keys provided by cloud KMS services (such as AWS KMS and Azure Key Vault) can typically only be configured for a single purpose — either signing/verification or encryption/decryption — but not both at the same time. Could you please clarify whether your implementation is intended to integrate with cloud-based KMS / HSM services, or with a self-managed key management system? |
|
@xigaoku With a self-managed key management system |
|
@heng-gezhizou We’re very happy to receive your reply—it has addressed our questions clearly. To ensure the SDK remains extensible after the changes, we would like to have a deeper discussion with you about the PR adjustment plan. The overall approach is as follows:
Existing SDK class diagram (simplified): ---
config:
class:
hideEmptyMembersBox: true
---
classDiagram
class RequestInterceptor{
-String rsaPrivateKey;
}
RequestInterceptor ..> RsaUtil
class ResponseBodyConverter{
-String rsaPrivateKey;
}
ResponseBodyConverter ..> RsaUtil
class RsaUtil
PR adjustment class diagram (simplified): ---
config:
class:
hideEmptyMembersBox: true
---
classDiagram
class KeyProvider {
<<interface>>
+sign(String content) String
+signPSS(String content) String
+decrypt(String content, RSATypeEnum rsaType) byte[]
}
class RequestInterceptor {
- KeyProvider provider
}
RequestInterceptor --> KeyProvider
class SafeheronConfig {
-KeyProvider provider
+getKeyProvider() KeyProvider
+keyProvider(KeyProvider provider)
+rsaPrivateKey(String privateKey)
}
SafeheronConfig o-- KeyProvider
class ResponseBodyConverter {
- KeyProvider provider
}
ResponseBodyConverter --> KeyProvider
class DefaultPrivateKeyProvider {
-String rsaPrivateKey;
+sign(...) String
+signPSS(...) String
+decrypt(...) byte[]
}
class SelfManagedKeyProvider:::extendClass {
-String keyId
+sign(...) String
+signPSS(...) String
+decrypt(...) byte[]
}
KeyProvider <|.. DefaultPrivateKeyProvider
KeyProvider <|.. SelfManagedKeyProvider
class RsaUtil
DefaultPrivateKeyProvider ..> RsaUtil
classDef extendClass fill:#EEEEEE,stroke:,stroke-dasharray: 3 3
Built-in public final class DefaultPrivateKeyProvider implements KeyProvider {
private final String privateKey;
public DefaultPrivateKeyProvider(String privateKey) {
this.privateKey = Objects.requireNonNull(privateKey, "privateKey must not be null");
}
@Override
public String sign(String content) {
// Use RsaUtil for signing
return "signature";
}
@Override
public String signPSS(String content) {
// Use RsaUtil for signing
return "pss-signature";
}
@Override
public byte[] decrypt(String content, RSATypeEnum rsaType) {
// Use RsaUtil for decryption
return new byte[0];
}
}Usage examples:
SafeheronConfig config = SafeheronConfig.builder()
.baseUrl("...")
.apiKey("...")
.safeheronRsaPublicKey("...")
// internally instantiates DefaultPrivateKeyProvider
.rsaPrivateKey("...")
.build();
TransactionApiService transactionApi = ServiceCreator.create(TransactionApiService.class, config);
SafeheronConfig config = SafeheronConfig.builder()
.baseUrl("...")
.apiKey("...")
.safeheronRsaPublicKey("...")
// If both `keyProvider` and `rsaPrivateKey` are provided,
// the `keyProvider` configuration takes precedence over `rsaPrivateKey`.
.keyProvider(new SelfManagedKeyProvider(keyId))
.build();
TransactionApiService transactionApi = ServiceCreator.create(TransactionApiService.class, config);Benefits of this approach:
Additional note: The current proposal does not involve cloud-based KMS/HSM. References to KMS/HSM in the PR or code comments should be replaced with Self-Managed Key Management to avoid confusion. Please review this proposed adjustment. We hope to further discuss this PR with you, and if you agree with our suggestions, you can modify the PR accordingly and resubmit. |
- Add KeyProvider interface for custom signing/decryption. - Support keyProvider injection in SafeheronConfig. - Decouple SDK core from RSA private key strings. - Update demo and terminology to "Self-Managed Key Management".
|
@xigaoku Thank you for the detailed explanation and the clear adjustment plan. I’ve updated the PR according to the proposed design and have pushed the latest changes to the designated branch. The current implementation introduces the KeyProvider interface as the unified extension point for signing and decryption, adds the built-in DefaultPrivateKeyProvider to preserve existing behavior, and updates SafeheronConfig, RequestInterceptor, and ResponseBodyConverter to depend on KeyProvider instead of directly on the private key or RsaUtil. Backward compatibility is maintained by keeping the rsaPrivateKey configuration, with keyProvider taking precedence when both are provided, as suggested. I’ve also adjusted the related comments and naming to use “Self-Managed Key Management” and removed references to KMS/HSM to avoid confusion. Please take a look at the latest commit and let me know if there are any further adjustments or refinements you’d like to discuss. I’m happy to iterate based on your feedback. |
|
@heng-gezhizou Thank you. We will review it as soon as possible and get back to you. |
|
@heng-gezhizou We really appreciate the time you put into this PR. We’ve reviewed the changes and put together a few suggestions. We tried to cover everything in one pass, though there may still be some gaps. Please feel free to share your thoughts — we’re very happy to iterate and refine this together.
If you agree with these suggestions, feel free to update the PR accordingly. Of course, we’re also open to discussing alternatives if you have different ideas — thanks again for the great contribution, and we’re looking forward to continuing the collaboration! |
Background
The current SDK requires configuring the complete RSA private key in the local
configuration file. However, in many production environments, RSA private keys
are managed by secure key management systems such as HSM or KMS, where private
key material must not be exported or exposed to the application layer.
This limitation makes it difficult to integrate the SDK in security- and
compliance-sensitive environments.
Solution
This PR introduces an extension mechanism that allows RSA cryptographic
operations to be delegated to external signing or key management systems.
A new
ExternalRsaProviderinterface is added to abstract RSA signing anddecryption capabilities, enabling the SDK to work with keys identified by
keyIdinstead of raw private key content.Changes
ExternalRsaProviderinterface for external RSA operationskeyIdrather than a private key stringExternalRsaProviderExternalRsaProviderUse Cases
Compatibility
ExternalRsaProvideris not configuredNotes
The test implementation uses a mock provider with local RSA signing for
demonstration purposes only. In production, the provider should delegate all
cryptographic operations to a secure external system.