Skip to content

Feat: secure cert support#2629

Draft
JillieBeanSim wants to merge 51 commits intozowe:masterfrom
JillieBeanSim:feat/secure-cert-support
Draft

Feat: secure cert support#2629
JillieBeanSim wants to merge 51 commits intozowe:masterfrom
JillieBeanSim:feat/secure-cert-support

Conversation

@JillieBeanSim
Copy link
Contributor

@JillieBeanSim JillieBeanSim commented Oct 20, 2025

What It Does

macOS & Windows:
Core Features:
Certificate retrieval from macOS Keychain or Credential Manager via certAccount property
Profile validation recognizing certAccount/certKeyAccount
User prompting for certificate access authorization
Comprehensive error handling with fallback guidance

macOS Known Limitation:
Private key export blocked by macOS Security Framework (documented with workarounds)

Extra Documentation:
Certificate_Keychain_Limitations.md - Private key export limitations and 4 workaround options
Certificate_Access_Prompting.md - User prompting configuration guide

How to Test

Review Checklist
I certify that I have:

  • updated the changelog
  • manually tested my changes
  • added/updated automated unit/integration tests
  • created/ran system tests (provide build number if applicable)
  • followed the contribution guidelines

Additional Comments

Screenshot 2025-12-09 at 11 50 59 AM Screenshot 2025-12-09 at 11 52 04 AM if key not exportable: Screenshot 2025-12-09 at 11 54 28 AM

Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
…pport

Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
@JillieBeanSim JillieBeanSim self-assigned this Oct 20, 2025
@github-project-automation github-project-automation bot moved this to New Issues in Zowe CLI Squad Oct 20, 2025
@zowe-robot zowe-robot moved this from New Issues to In Progress in Zowe CLI Squad Oct 20, 2025
@codecov
Copy link

codecov bot commented Oct 20, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.81%. Comparing base (0141361) to head (b5f5051).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2629      +/-   ##
==========================================
- Coverage   91.84%   91.81%   -0.03%     
==========================================
  Files         645      645              
  Lines       19281    19602     +321     
  Branches     4172     4396     +224     
==========================================
+ Hits        17708    17998     +290     
- Misses       1571     1602      +31     
  Partials        2        2              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

… lint errors

Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
@JillieBeanSim JillieBeanSim marked this pull request as ready for review December 9, 2025 16:57
@zowe-robot zowe-robot moved this from In Progress to Review/QA in Zowe CLI Squad Dec 9, 2025
@traeok traeok self-requested a review December 10, 2025 16:04
traeok
traeok previously requested changes Dec 16, 2025
Copy link
Member

@traeok traeok left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this Billie, left a couple requests for my initial review. Going to manually test once the hardcoded path comment is resolved

JillieBeanSim and others added 3 commits December 18, 2025 07:34
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
@t1m0thyj t1m0thyj self-requested a review December 18, 2025 21:59
@traeok traeok self-requested a review December 22, 2025 13:28
Signed-off-by: Billie Simmons <BillieJean.Simmons@ibm.com>
@traeok traeok dismissed their stale review December 22, 2025 14:31

Requested changes were implemented

Copy link
Member

@traeok traeok left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this Billie. I was wondering if there were certain cases that you'd want us to test for the "How to test" section of the PR? I noticed it was empty so I started to follow based off of the writeup in Certificate_Access_Prompting.md.

I left a comment based on my experience while testing thus far

JillieBeanSim and others added 3 commits December 22, 2025 15:13
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com>
@JTonda JTonda requested a review from traeok January 7, 2026 16:05
Signed-off-by: Billie Simmons <BillieJean.Simmons@ibm.com>
CERT_SYSTEM_STORE_LOCAL_MACHINE,
];

for &_store_location in &store_locations {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_store_location is unused here, is this expected? If so, we could remove the outer for loop. Otherwise the inner logic may need refactored to consider the different store locations.

Comment on lines +509 to +521
// If not found by subject, try enumerating all certificates
if cert_context.is_null() {
cert_context = unsafe {
CertEnumCertificatesInStore(h_store, std::ptr::null())
};
}

while !cert_context.is_null() {
// Try to get the private key for this certificate
if let Ok(Some(key_bytes)) = extract_private_key_from_cert(cert_context) {
unsafe { CertCloseStore(h_store, 0) };
return Ok(Some(key_bytes));
}
Copy link
Member

@traeok traeok Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Windows, if a specific match isn't found by subject, the logic falls back to enumerating all certificates. This makes it possible for an unrelated certificate to be returned just because it has a valid private key, which might not match the intended host. Is this intended behavior? I noticed with the macOS logic, if the specific account isn't found, it returns None.

Should we consider refining the certificate search or removing it entirely if it may return a mismatched certificate?

/// - A `KeyringError` indicating that this feature is not supported on Linux/Unix
///
pub fn get_certificate(service: &String, account: &String, _optional: bool) -> Result<Option<Vec<u8>>, KeyringError> {
Err(KeyringError::Os(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though the API isn't implemented for Linux, I'm wondering if we should return Ok(None) when optional is true 🤔

That way we can gracefully fallback if the request was optional under that OS. This is more from the perspective of secrets_core or the Secrets SDK being used directly by developers.

Comment on lines +317 to +320
let data = CFData::wrap_under_create_rule(exported_data as *mut _);
let mut bytes = Vec::new();
bytes.extend_from_slice(data.bytes());
return Ok(Some(bytes));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed certificates are exported as PEM (expected), but private keys are exported as raw DER format. For Windows, however, the implementation exports both in PEM format. Should we match the export format for private keys with the Windows implementation?

/// Extract the private key from a certificate context and return it in PEM format
fn extract_private_key_from_cert(cert_context: *const CERT_CONTEXT) -> Result<Option<Vec<u8>>, KeyringError> {
// Helper to wrap DER into PEM header/footer
fn der_to_pem(label: &str, der: &[u8]) -> Vec<u8> {
Copy link
Member

@traeok traeok Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The der_to_pem helper could be reused/generalized to apply to both the Windows and macOS implementation (to have matching private key export formats)

Copy link
Member

@t1m0thyj t1m0thyj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @JillieBeanSim for working on this! Left a few questions below. I plan to review further and do manual testing soon 😋

### Error Message

```
Private key export not supported - identity found but key cannot be exported for account 'User BJSIMM on tvt4002'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we intend for these docs to be user-facing, can we use a generic username (and system name if applicable)?

Suggested change
Private key export not supported - identity found but key cannot be exported for account 'User BJSIMM on tvt4002'
Private key export not supported - identity found but key cannot be exported for account 'User IBMUSER on lpar1'

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for including this file to explain the design and make review easier 🙂 I assume before the PR is merged, we'll want to either move it to the docs directory, or remove it if intended to be temporary?

// properties should call the async APIs directly.
// Fire-and-forget the resolver so background work (prompts) can proceed
// without changing the synchronous contract.
void ConnectionPropsForSessCfg.resolveSessCfgProps(sessCfg, cmdArgs, connOpts);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally I think we prefer to avoid using void keyword on async functions where possible, but in this case I assume it's needed to avoid breaking changes as described in the comment?

Is it also correct that the only prompt that can happen here is a question asking the user to grant certificate access? So in worst case scenario if we don't wait for the method to resolve, then I assume we end up with a session that is missing certificate-related properties.

return candidates;
}

private static writeCertToTempFile(sessCredName: string, certBuf: Buffer): string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Writing the certs to a temp file makes me a bit uneasy, would prefer if we can avoid doing so. If we want to guarantee that certs are handled securely, then ideally we shouldn't store them on disk at all. And having to track these temp file paths in memory and dispose of the files later on adds extra overhead.

I assume the main reason for storing them in files was to avoid breaking changes, and fit the ISession type that expects cert and certKey to be file paths. Some alternative approaches that could work:

  • Add optional properties to ISession for certLoaded and certKeyLoaded that contain the values of the certificates cached in memory.
    • Pro: As long as the properties are optional, they shouldn't cause a breaking change.
    • Con: It may be confusing to have duplicate cert properties on the session, but this could be alleviated by making cert/certKey and certLoaded/certKeyLoaded mutually exclusive.
  • Change the types of cert and certKey properties on ISession from string to string | Buffer where a buffer contains loaded contents rather than a file path.
    • Pro: This approach may be familiar to developers - I believe it's similar to other packages like zos-node-accessor upload method.
    • Con: This may be considered too much of a breaking change, even if we keep the behavior for string case the same.

@JillieBeanSim
Copy link
Contributor Author

JillieBeanSim commented Jan 14, 2026

hey all, thanks for the reviews @t1m0thyj & @traeok. I have a request from customer to support certs where the key wasn't set as exportable. So I will be looking into that and reworking the proposed solution.

@JillieBeanSim JillieBeanSim marked this pull request as draft January 15, 2026 13:54
@zowe-robot zowe-robot moved this from Review/QA to In Progress in Zowe CLI Squad Jan 15, 2026
Signed-off-by: Billie Simmons <BillieJean.Simmons@ibm.com>
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
3.9% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

Secrets SDK: Add ability to load client certs from OS cert storage

5 participants