Skip to content

Generate the StudioAPI proto at build time and complete authentication - #20

Open
stefanrammo wants to merge 1 commit into
masterfrom
feature/generated-proto-and-version-gating
Open

stefanrammo wants to merge 1 commit into
masterfrom
feature/generated-proto-and-version-gating

Conversation

@stefanrammo

Copy link
Copy Markdown
Collaborator

StudioAPI.java is generated from a vendored studioapi.proto by protobuf-maven-plugin (protoc 3.25.5), the hand-committed copy is dropped. protobuf-java 3.21.5 -> 3.25.5. Version 2.0.0: AuthRequest gains getSuggestedUsers(). The Readme and pom descriptions mention authentication.

Authentication (RSA EncryptedPassword/LDAP and PasswordHash):

  • Deliver the additional challenges of an AuthResponse to the AuthRequest. Map result code 0 to CREDENTIALS_REQUIRED and a missing result_code to INVALID_CHALLENGE_RESPONSE. Fill rolesAssigned from role_assigned.
  • Answer the server's EncryptedPassword request from a copy of the credentials, forgotten once granted. An empty password sends no hash response, an empty username builds no request.
  • PasswordHash, NewPasswordHash and challengeHash lowercase ASCII 'A'-'Z' only and encode UTF-8, so the digest matches the server's on any JVM locale and default charset.
  • One prompt per re-authentication cycle, answering the server's latest challenge. Each connection remembers the prompt whose answer it last received: a rejected or username-less answer opens a correction prompt for the connections it failed on, a cycle start joins the current prompt, and an answered prompt older than five seconds is dropped before any request is routed. Report the reauth round's own result at handshake acceptance. Expose Hello.suggested_users. Report a missing username as "Username required".

TLS: Transport.onSetSSLParameters calls super first, so setTrustedCertificates(certs, true) verifies the server host name and false disables it. Its javadoc describes the check and the generated StudioAPI.crt's subject.

Values: eUSHORT/eUCHAR/eSHORT/eCHAR parse numerically, eUINT/eUINT64 parse and print unsigned, the clock delta applies to a non-zero timestamp only.

Unit tests cover auth, re-authentication (ClientReauthTest, 15 cases), Hello parse, TLS parameters and value decoding.

CDP-5665

StudioAPI.java is generated from a vendored studioapi.proto by protobuf-maven-plugin
(protoc 3.25.5), the hand-committed copy is dropped. protobuf-java 3.21.5 -> 3.25.5.
Version 2.0.0: AuthRequest gains getSuggestedUsers(). The Readme and pom descriptions
mention authentication.

Authentication (RSA EncryptedPassword/LDAP and PasswordHash):
- Deliver the additional challenges of an AuthResponse to the AuthRequest. Map result code 0
  to CREDENTIALS_REQUIRED and a missing result_code to INVALID_CHALLENGE_RESPONSE. Fill
  rolesAssigned from role_assigned.
- Answer the server's EncryptedPassword request from a copy of the credentials, forgotten once
  granted. An empty password sends no hash response, an empty username builds no request.
- PasswordHash, NewPasswordHash and challengeHash lowercase ASCII 'A'-'Z' only and encode
  UTF-8, so the digest matches the server's on any JVM locale and default charset.
- One prompt per re-authentication cycle, answering the server's latest challenge. Each
  connection remembers the prompt whose answer it last received: a rejected or username-less
  answer opens a correction prompt for the connections it failed on, a cycle start joins the
  current prompt, and an answered prompt older than five seconds is dropped before any request
  is routed. Report the reauth round's own result at handshake acceptance. Expose
  Hello.suggested_users. Report a missing username as "Username required".

TLS: Transport.onSetSSLParameters calls super first, so setTrustedCertificates(certs, true)
verifies the server host name and false disables it. Its javadoc describes the check and
the generated StudioAPI.crt's subject.

Values: eUSHORT/eUCHAR/eSHORT/eCHAR parse numerically, eUINT/eUINT64 parse and print
unsigned, the clock delta applies to a non-zero timestamp only.

Unit tests cover auth, re-authentication (ClientReauthTest, 15 cases), Hello parse, TLS
parameters and value decoding.

CDP-5665
@Karmo7

Karmo7 commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

If the PR is about adding authentication then @martlaak is probably the best to review it.

if (valueType == CDPValueType.eUNDEFINED) return "<invalid variant>";
if (valueType == CDPValueType.eUINT)
return Integer.toUnsignedString((Integer) value);
if (valueType == CDPValueType.eUINT64)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What about eUCHAR and eUSHORT?

}

/** Get the Variant's value as a printable String. */
/** Get the Variant's value as a printable String. Unsigned types print their unsigned value. */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not sure the comment change is useful

break;
case eUSHORT:
pbv.setUsValue((Short) value.getValue());
pbv.setUsValue(((Number) value.getValue()).intValue());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What was wrong with the existing code?

import java.util.function.BiConsumer;

/**
* Main Client class for initializing the CDP Java client.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should this here or the overview.html be updated with the new APIs and auth methods? Those two are the main getting started guides and the latter is the landing page of javadoc

@martlaak

Copy link
Copy Markdown
Contributor

Verdict: approve with minor follow-ups. The wire format matches CDP securitylib, StudioAPIServer and the canonical studioapiprotobuf/studioapi.proto. No blocking defects found. Reviewed by reading the code against the C++ sources; the Java build and tests were not run as part of this review.

Verified compatible with CDP

  • Proto – vendored src/main/proto/studioapi.proto is byte-identical to the canonical file and already carries java_package / java_outer_classname, so the generated class lands in com.cdptech.cdpclient.proto.StudioAPI as before.
  • PasswordHashsha256(challenge + ":" + sha256(lower(user) + ":" + password)) sent as raw digest bytes matches CDPMeta::Security::Hash::challengeHash and PasswordAuthentication::Authenticate. NewPasswordHash sends the raw inner digest; the server hex-encodes it itself (SetLocalPasswordHash(toHexString(...))).
  • Lowercasing / encoding – ASCII-only A–Z lowercasing matches the server's per-byte ::tolower. UTF-8 matches since the server hashes raw std::string bytes and challenges are alphanumeric (randomAlphanum(16)).
  • EncryptedPassword – plaintext challenge + password, RSA/ECB/PKCS1Padding, 245-byte chunks, 2048-bit key: identical to Crypto::Password::encrypt / decrypt (RSA_PKCS1_PADDING, KeyLenBits / 8 - 11). The server emits the key with PEM_write_bio_PUBKEY (SubjectPublicKeyInfo), so X509EncodedKeySpec is the right parser.
  • Reissue with the original challenge is correctConnectionAuthenticator generates a challenge only at Hello and in SendAuthenticationExpiredError, never after an AdditionalResponseRequired round. LDAPAuthentication compares the decrypted prefix against that same challenge.
  • Both PasswordHash and EncryptedPassword in the reissue matches the C++ UserAuthenticateProtocol and lets PasswordAuthentication bypass to LDAP via CredentialsRequired.
  • Reauth answers the latest challenge – real bug fix. The server regenerates the challenge on every eAUTH_RESPONSE_EXPIRED; the old code froze the challenge at prompt time, so a burst of expiry errors made the reauth fail.
  • Missing result_code → denial matches the C++ client. Additional challenges now reach the AuthRequest – the old loop built Credential objects but never added them to the list.
  • TLSTrustingSSLSocketFactory uses an X509ExtendedTrustManager, so enabling HTTPS endpoint identification by default does not break setIgnoreCertificates(true).

Findings (low severity)

  1. No guard on the automatic EncryptedPassword reissue
    AuthenticationProtocol.java:31, IOHandler.java:238, Authenticator.java:114
    The client reissues whenever the response carries an EncryptedPassword challenge with a key, regardless of result_code and with no retry limit. Today's server only sends the key with AdditionalResponseRequired, so this is not reachable, but a misbehaving or future server would cause a request loop. Suggest: reissue only when the code is ADDITIONAL_RESPONSE_REQUIRED and at most once per attempt (reset in authenticate() / on eAUTH_RESPONSE_EXPIRED). The same issue existed in the C++ UserAuthenticateProtocol and has been fixed there.

  2. Plaintext password retained after a failed attempt
    Authenticator.java:65, Authenticator.java:105
    lastCredentials is cleared only on a grant (Connection calls clearCachedCredentials() on GRANTED / GRANTED_PASSWORD_WILL_EXPIRE_SOON). After a rejected attempt the copy stays until the next attempt or connection close. Clearing it on any terminal non-AdditionalResponseRequired result would shorten the window.

  3. Test fidelity nit
    AuthenticatorTest.java:263-267
    encryptedPasswordRequestWithoutKey_surfacesTheResult uses eCredentialsRequired, but SecurityLib::AuthRequest::Resolve rewrites a final CredentialsRequired into InvalidChallengeResponse, so a real server sends code 11 here. The client handles both identically; consider using eInvalidChallengeResponse so the test mirrors the wire.

Behavior changes (fine for 2.0.0, please mention in release notes)

  • Host name verification is now on by default (Transport.java:81) for any socket factory passed via Client.setSocketFactory(factory, null) (Client.java:195). Previously the override never called super, so setTrustedCertificates(certs, true) silently did not verify the host. Users connecting by IP with a custom trust store will now need to disable it in their parameter handler.
  • Narrow integer boxing (Variant.java:111-116) – eUSHORT, eSHORT, eUCHAR, eCHAR are all boxed as Integer now, and eUCHAR parses a number instead of charAt(0). Callers using variant.<Short>getValue() / <Byte>getValue() on Builder-created variants will get a ClassCastException. Server-received values were already Integer, so this is a consistency fix.
  • AuthRequest.getSuggestedUsers() (AuthRequest.java:94) is a new abstract method on a public interface; external implementors of AuthRequest must add it.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants