-
Notifications
You must be signed in to change notification settings - Fork 5
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
base: jdk12-master
Are you sure you want to change the base?
Conversation
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(); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok.
There was a problem hiding this comment.
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>(); |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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>(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
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(...)) ...
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Include uce
as cause.
src/jdk.security.auth/share/classes/com/sun/security/auth/module/GssLoginModule.java
Show resolved
Hide resolved
return; | ||
} | ||
if (doNotPrompt) | ||
throw new LoginException("Unable to prompt for password\n"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the \n
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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++) |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Report the cause.
Great code review commentary! Thanks! |
* {@link GSSException#FAILURE GSSException.FAILURE} | ||
*/ | ||
public abstract GSSCredential createCredential (GSSName name, | ||
String password, int lifetime, Oid mech, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, ok.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) {
...
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
@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 |
@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. |
@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. |
Max has expressed to me the possibility that when this is all done he might rip out the Java-coded If we could also get JAAS ripped out that would be great! What use is JAAS anyways, in a world with no applets? |
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. |
@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! :( |
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. |
@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. |
Yes, it luckily is. I need to figure out how to compile this on Windows. |
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:
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. |
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 |
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. |
Another thing that's a disaster is the |
By ignoring |
Say your app wants TCP-style ordered delivery, no replays, this this is the right way to do it:
while this is very very wrong:
The problem is that The |
Ouch, that's horrible. Does it also fail with |
No, the only functions that, today, return supplementary major status codes are:
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. |
|
No description provided.