Skip to content
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

Code review of ts jgss jdk12 #1

Draft
wants to merge 25 commits into
base: jdk12-master
Choose a base branch
from
Draft

Code review of ts jgss jdk12 #1

wants to merge 25 commits into from

Conversation

pburka
Copy link
Member

@pburka pburka commented Mar 4, 2019

No description provided.

Viktor Dukhovni and others added 25 commits September 26, 2018 10:43
Also improve object size handling in NativeUtil.
Also avoid memory allocation in newGSSOIDSet() renamed to makeGSSOIDset()
which now takes a singleton set argument and either assigns the requested
OID or with SPNEGO returns a static list of all the supported mechs. With
this we no longer need deleteGSSOIDSet().
We must not dispose() of any credential handle passed to the
NativeGSSContext() constructor.  But we must dispose() of credentials
that are acquired in NativeGSSContext.

This is very important because the JVM does not know about the size of
the JNI credential objects, so it doesn't readily recognize memory
pressure from them, leading to memory pressure issues in SASL and GSS
server applications.
There is only one token that we can extract an actual mechanism OID from
in the SPNEGO case when the native GSS library doesn't provide that
(though it should) in the API.  If the SPNEGO exchange ends up requiring
more than two tokens, then the previous code failed to establish a
security context.

Also, never raise if we cannot get an actual mech OID from SPNEGO
tokens.
Also use empty realm as wildcard for krbtgt names
This module is to be used for GSS applications in preference to
Krb5LoginModule, especially when using the native GSS provider.
Also don't force same name for acceptor and initiator.
Add commentary about native in Krb5LoginModule
We were reacquiring the initiator/acceptor credential upon security
context full establishment in order to indirectly perform a permission
check on the srcName/targName once we find out what they are.  But this
is just one more way to end up failing, which happens with Heimdal when
using SPNEGO because we ask to acquire a Kerberos credentials using a
SPNEGO MN and that fails.

Also, there was a security bug here: if the permission check fails then
we raise, but if the application already has a context handle, then it
can use it anyways if it catches the exception!  The fix for this is to
dispose() when the permission check fails.
Native objects are memory icebergs: they are much larger than the JVM
knows, so the GC might not dispose of them soon enough.
this.getName().startsWith("krbtgt/")))
return true;

char[] n = this.getName().toCharArray();
Copy link
Member Author

Choose a reason for hiding this comment

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

Converting this to a char[] is an unnecessary copy. Just operate on the String directly.

continue;
}
if (n[i] == '@') {
String s = new String(n, 0, i + 1);
Copy link
Member Author

Choose a reason for hiding this comment

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

This allocation is unnecessary. Use String#regionMatches instead.

Copy link
Member

Choose a reason for hiding this comment

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

Ok.

Copy link
Member

Choose a reason for hiding this comment

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

Ok.

debug("Skipped name " + name + " due to " + ge);
}
}
Set<GSSName> names = new HashSet<GSSName>();
Copy link
Member Author

Choose a reason for hiding this comment

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

Use new HashSet<>() (it's no longer necessary to repeat GSSName here).

Copy link
Member

Choose a reason for hiding this comment

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

Ah, this is from having forward-ported from JDK 7.

return result;
}

result = new Vector<T>();
Copy link
Member Author

Choose a reason for hiding this comment

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

I know this is pre-existing, but Vectors are very 1998. Consider using an ArrayList.

Copy link
Member

Choose a reason for hiding this comment

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

I might leave this one in the interest of time.

Iterator<GSSCredentialImpl> iterator =
accSubj.getPrivateCredentials
(GSSCredentialImpl.class).iterator();
while (iterator.hasNext()) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Again, pre-existing code, but consider using a for-each here. (for (GSSCredentialImpl cred : accSubj.getPrivateCredentials(...)) ...

Copy link
Member

Choose a reason for hiding this comment

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

Agreed.

Object[] source = {defUsername};
callbacks[0] = new NameCallback(form.format(source));
callbackHandler.handle(callbacks);
name = ((NameCallback)callbacks[0]).getName();
Copy link
Member Author

Choose a reason for hiding this comment

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

This feels needlessly roundabout. Just store the callback in an appropriately typed local.

if (name != null && name.length() == 0)
name = null;
if (name == null && defUsername != null &&
defUsername.length() != 0)
Copy link
Member Author

Choose a reason for hiding this comment

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

isEmpty()

defUsername.length() != 0)
name = defUsername;
} catch (java.io.IOException ioe) {
throw new LoginException(ioe.getMessage());
Copy link
Member Author

Choose a reason for hiding this comment

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

Include ioe as cause.

} catch (java.io.IOException ioe) {
throw new LoginException(ioe.getMessage());
} catch (UnsupportedCallbackException uce) {
throw new LoginException
Copy link
Member Author

Choose a reason for hiding this comment

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

Include uce as cause.

return;
}
if (doNotPrompt)
throw new LoginException("Unable to prompt for password\n");
Copy link
Member Author

Choose a reason for hiding this comment

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

Remove the \n

Copy link
Member

Choose a reason for hiding this comment

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

Ay!

getAuthResourceString(
"Kerberos.password.for.username."));
Object[] source = {name};
callbacks[0] = new PasswordCallback(form.format(source), false);
Copy link
Member Author

Choose a reason for hiding this comment

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

ditto -- alloc and initialize on one line.

Object[] source = {name};
callbacks[0] = new PasswordCallback(form.format(source), false);
callbackHandler.handle(callbacks);
char[] tmpPassword = ((PasswordCallback)
Copy link
Member Author

Choose a reason for hiding this comment

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

ditto. Use a temp instead of casting.

((PasswordCallback)callbacks[0]).clearPassword();

// clear tmpPassword
for (int i = 0; i < tmpPassword.length; i++)
Copy link
Member Author

Choose a reason for hiding this comment

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

Arrays.fill(tmpPassword, ' ')

for (int i = 0; i < tmpPassword.length; i++)
tmpPassword[i] = ' ';
} catch (java.io.IOException ioe) {
throw new LoginException(ioe.getMessage());
Copy link
Member Author

Choose a reason for hiding this comment

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

Report the cause.

@nicowilliams
Copy link
Member

Great code review commentary! Thanks!

* {@link GSSException#FAILURE GSSException.FAILURE}
*/
public abstract GSSCredential createCredential (GSSName name,
String password, int lifetime, Oid mech,

Choose a reason for hiding this comment

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

Shouldn't the password be a char array?

Copy link
Member

Choose a reason for hiding this comment

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

Why should it be a char array and not a String?

Choose a reason for hiding this comment

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

To securely wipe the password from memory against immutable strings in Java. In C you can safely to memset with zero, you Java you can't.

Copy link
Member

Choose a reason for hiding this comment

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

Ahh, ok.

Copy link
Member

Choose a reason for hiding this comment

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

Is that reliable? @pburka suggests that a copying GC can easily leave copies of a password lying about. And in the JAAS case the password would come from a file that is read in and parsed, so there will be a number of copies. I'm not keen on reading the whole file as a char[] just so we could wipe the password.

Choose a reason for hiding this comment

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

What file are you talking about? With JAAS you have callback handlers and they use char arrays too.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, maybe I was confused. Krb5LoginModule let's you specify a raw secret key -- that's a bit crazy, IMO. Still, other login modules use String for passwords too -- at least JndiLoginModule.

Choose a reason for hiding this comment

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

How if I look at this?

Copy link
Member

Choose a reason for hiding this comment

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

From src/jdk.security.auth/share/classes/com/sun/security/auth/module/JndiLoginModule.java

543                 // check the password
544                 if (verifyPassword
545                     (encryptedPassword, new String(password)) == true) {
...
710     /**
711      * Verify a password against the encrypted passwd from /etc/shadow
712      */
713     private boolean verifyPassword(String encryptedPassword, String password) {
...

Choose a reason for hiding this comment

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

Ok, I see, that's ugly. You'll see a lot of such things in the JDK. Old Code (TM).

* {@link GSSException#FAILURE GSSException.FAILURE}
*/
public abstract GSSCredential createCredential(GSSName name,
String password, int lifetime,

Choose a reason for hiding this comment

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

Same here.

@@ -135,12 +135,26 @@ public GSSCredential createCredential(GSSName aName,
return wrap(new GSSCredentialImpl(this, aName, lifetime, mech, usage));
}

public GSSCredential createCredential(GSSName aName, String password,

Choose a reason for hiding this comment

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

Same here.

}
return new AppConfigurationEntry[] {
new AppConfigurationEntry(
"com.sun.security.auth.module.Krb5LoginModule",
"com.sun.security.auth.module.GssLoginModule",

Choose a reason for hiding this comment

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

This is a change in behavior, why?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, it is. Whether this is good or not will be up to Oracle. GssLoginModule isn't quite on par with Krb5LoginModule's functionality -- we'd have to add support for using the new gss_acquire_cred_from() functions in MIT and Heimdal in order to add support for the missing functionality, which is all about acquiring credentials with raw passwords or raw keys.

Choose a reason for hiding this comment

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

That's fine then!

@@ -108,9 +108,17 @@ public GSSNameSpi getNameElement(byte[] name, Oid nameType)
}

public GSSCredentialSpi getCredentialElement(GSSNameSpi name,
int initLifetime, int acceptLifetime,
String password, int initLifetime, int acceptLifetime,

Choose a reason for hiding this comment

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

Same here.

@nicowilliams
Copy link
Member

@michael-o question: are you reviewing these changes as a participant, or are you interested in the functionality? If you're interested in the functionality, have you tried these changes? Do they work for you?

We've been using some iteration on these changes from JDK 7 to 11.

We needed this mostly because without making GSSName extend Principal, native-C GSS cannot be used with JAAS, and making GSSName extend Principal implies adding a GssLoginModule.

@michael-o
Copy link

@nicowilliams Most of functionality. I have been carefully watching your contributions on security-dev as well as your comments on SSPI JGSS Provider. I also have been in contact with several times with Max issues in JGSS in General and am generally interesting in a better JGSS because it misses a lot of stuff like enterprise principals, referrals, etc.

I have not yet tried it, because my regular deployment platforms are HP-UX and FreeBSD where I do not have Java 11 at the moment. Though, I do develop on Windows.

@nicowilliams
Copy link
Member

nicowilliams commented Mar 9, 2019

@michael-o fascinating! Thanks for the information.

FYI, since you mention Windows, we do use these patches on Windows as well using Martin Rex's (SAP) "gsskrb5" GSS->SSPI bridge, which we also patch. You can find our copy of gsskrb5, with our patches, at https://github.com/twosigma/gsskrb5.

@nicowilliams
Copy link
Member

Max has expressed to me the possibility that when this is all done he might rip out the Java-coded Krb5 bits from the OpenJDK, which I think would be a very good thing to do.

If we could also get JAAS ripped out that would be great! What use is JAAS anyways, in a world with no applets?

@michael-o
Copy link

michael-o commented Mar 9, 2019

I am aware of these patches, and would like to try them, I haven't yet found how to get started. Actually, I do believe that we should have as little shims as possible => Java > SSPI instead of Java > GSS-API (C) > SSPI. I am not completely convinced by Max' approach, he's having too much wrapper Code. E.g., wrapping Kerberos tokens in SPNEGO Tokens with his own ASN.1 Encoder. That's not right, I'd handle as much as possible over to SSPI.

Moving the code out and improving it would be a great imrovement as well as getting rid of JAAS. Making heavy use of GSS-API in several OSS projects I do maintain in Java. I haven't found any real benefit compared to provide the path to the keytab directly. As well as many LDAP-related issued in JNDI along with Kerberos.

@nicowilliams
Copy link
Member

@michael-o Max's patches have changed dramatically. @lhoward explained to him how to get SSPI to do SPNEGO with only Kerberos. (I too very strongly objected to hand-coding DER codecs. His first attempt was riddled with severe bugs.)

I do agree that fewer shims == better, but Max's shim is now very, very thin, so I'm happy with that approach. Max still has to respond to my code review comments, as there are still issues. It probably would have been better for me to write a new, very thin GSS->SSPI shim instead of using Martin Rex's, but at the time it felt simpler to hack on Martin's shim. As it turned out, Martin's shim needed a lot of love too! :(

@michael-o
Copy link

michael-o commented Mar 9, 2019

I once started a new shim actually with JNI, but stalled all work due to time constraints :-(

The most problematic with a GSS-API to SSPI is the totally different approach to credential delegation and impersonation and its binding to an implicit thread-local storage.

I still only see https://cr.openjdk.java.net/~weijun/6722928/webrev.04/

I will kick into the review next week or so.

@nicowilliams
Copy link
Member

@michael-o Look carefully. All the DER codec stuff is gone from https://cr.openjdk.java.net/~weijun/6722928/webrev.04/src/java.security.jgss/windows/native/libsspi_bridge/sspi.cpp.html -- thank goodness.

@michael-o
Copy link

Yes, it luckily is. I need to figure out how to compile this on Windows.

@nicowilliams
Copy link
Member

I would say the most important difference between GSS and SSP is the handling of names.

In GSS names are opaque objects, with import, inquiry, duplicate, display/export, and release functions.

In SSPI names are strings, and you just have to know so much about their forms, and utility functions to "crack names" and what-not.

Both APIs are usability disasters, but GSS (especially with all its extensions) is much much better.

My ideal GSSv3 API, if we ever built one, would:

  • replace OM_uint32 *minor_status arguments with a call context akin to krb5_context
  • add gss_new_sec_context()
  • replace gss_init_sec_context() and gss_accept_sec_context() with setters to set various attributes of a new gss_ctx_id_t, then a gss_step_context() function to consume new input tokens and produce new output tokens
  • replace all the gss_inquire_* functions with getters that get one thing each
  • enhance buffer descriptors to have a release function pointer field
  • replace the channel bindings struct with a plain gss_buffer_t
  • all new functions would have a very small number of arguments

Most people who have complained to me about the complexity of GSS, mostly have complained about the number of arguments and number of structure fields. It's too complex. But more functions with meaningful names and fewer, simpler arguments, should work better -- it's always easy to ignore functions you don't need.

@nicowilliams
Copy link
Member

Ah, well, building the OpenJDK on Windows is a bit of an adventure... @vdukhovni does it here, mostly.

It helps to have a CI/CD that knows how to build for Windows, like Jenkins or Appeyor. But if you're like us, you'll need a contractual relationship to trust an external CI/CD, or else run it in-house.

As long as we have a clone on GitHub, we could write an appveyor.yml to make sure everything builds on Windows.

@michael-o
Copy link

I especially agree on point 2 and 3, most people never get the context completion right. I see tons of false information on the internet. But SSPI is even worse as an API here, old context in, new context pointer out. Horrible to handle.

@nicowilliams
Copy link
Member

Another thing that's a disaster is the GSS_ERROR() macro, which ignores supplementary status codes. Too many developers use it to check the result of gss_unwrap() and so fail to notice replay and out-of-order attacks! I've found more than half a dozen instances of that bug :(

@michael-o
Copy link

By ignoring minor_status?

@nicowilliams
Copy link
Member

Say your app wants TCP-style ordered delivery, no replays, this this is the right way to do it:

    major = gss_unwrap(&minor, sec_context, token, &plaintext, NULL, NULL);
    /* CORRECT */
    if (major != GSS_S_COMPLETE)
        /* handle error here */
        ...

while this is very very wrong:

    major = gss_unwrap(&minor, sec_context, token, &plaintext, NULL, NULL);
    /* WRONG!!! */
    if (GSS_ERROR(major))
        /* handle error here */
        ...

The problem is that GSS_ERROR() will evaluate to false even if major == GSS_S_DUPLICATE_TOKEN (replay), or major is any of GSS_S_OLD_TOKEN, GSS_S_UNSEQ_TOKEN, or GSS_S_GAP_TOKEN (out of order delivery.

The GSS_ERROR() macro should be removed. It's only useful and safe for checking the major status of gss_init_sec_context() and gss_accept_sec_context(). It must never be used for checking the result of gss_unwrap() or gss_verify_mic().

@michael-o
Copy link

Ouch, that's horrible. Does it also fail with gss_import_name or gss_delete_sec_context?

@nicowilliams
Copy link
Member

No, the only functions that, today, return supplementary major status codes are:

  • gss_init_sec_context() and gss_accept_sec_context(), which use GSS_S_CONTINUE_NEEDED
  • gss_unwrap() and gss_verify_mic(), which use
    • GSS_S_DUPLICATE_TOKEN (replay detection)
    • GSS_S_OLD_TOKEN, GSS_S_UNSEQ_TOKEN, and GSS_S_GAP_TOKEN (out-of-order delivery detection)

All other functions never indicate supplementary major status codes.

If we ever add async init/accept, and/or interactive credential acquisition (ala kinit), we might add new supplementary status codes, but right now, the above are all the uses of those.

@nicowilliams
Copy link
Member

GSS_ERROR() is just bad ergonomics. Best to remove 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.

None yet

3 participants