diff options
Diffstat (limited to 'source4/auth/kerberos')
-rw-r--r-- | source4/auth/kerberos/kerberos-notes.txt | 760 | ||||
-rw-r--r-- | source4/auth/kerberos/kerberos-porting-to-mit-notes.txt | 803 | ||||
-rw-r--r-- | source4/auth/kerberos/kerberos.h | 87 | ||||
-rw-r--r-- | source4/auth/kerberos/kerberos_credentials.h | 37 | ||||
-rw-r--r-- | source4/auth/kerberos/kerberos_pac.c | 496 | ||||
-rw-r--r-- | source4/auth/kerberos/kerberos_util.c | 648 | ||||
-rw-r--r-- | source4/auth/kerberos/krb5_init_context.c | 885 | ||||
-rw-r--r-- | source4/auth/kerberos/krb5_init_context.h | 79 | ||||
-rw-r--r-- | source4/auth/kerberos/srv_keytab.c | 406 | ||||
-rw-r--r-- | source4/auth/kerberos/wscript_build | 26 |
10 files changed, 4227 insertions, 0 deletions
diff --git a/source4/auth/kerberos/kerberos-notes.txt b/source4/auth/kerberos/kerberos-notes.txt new file mode 100644 index 0000000..6954129 --- /dev/null +++ b/source4/auth/kerberos/kerberos-notes.txt @@ -0,0 +1,760 @@ +Copyright Andrew Bartlett <abartlet@samba.org> 2005-2009 +Copyright Donald T. Davis <don@mit.edu> + +Released under the GPLv3 + +Important context for porting to MIT +------------------------------------ + +This document should be read in conjunction with the Samba4 source code. +DAL and KDC requirements are expressed (as an implementation against Heimdal's +HDB abstraction layer) in Samba4's source4/kdc/hdb-samba4.c in particular. +hbd-samba4.c is the biggest piece of samba-to-krb glue layer, so the main +part of the port to MIT is to replace hdb-samba4 with a similar glue layer +that's designed for MIT's code. + +PAC requirements are implemeneted in source4/kdc/pac-glue.c + +The plugins (both of the above are Heimdal plugins) for the above are loaded +in source4/kdc/kdc.c + +For GSSAPI requirements, see auth/gensec/gensec_gssapi.c (the consumer of +GSSAPI in Samba4) + +For Kerberos requirements, see auth/kerberos/krb5_init_context.c . + +Samba has its own credentials system, wrapping GSS creds, just as GSS +creds wrap around krb5 creds. For the interaction between Samba4 credentials +system and GSSAPI and Kerberos see auth/credentials/credentials_krb5.c . + +AllowedWorkstationNames and Krb5 +-------------------------------- + +Microsoft uses the clientAddresses *multiple value* field in the krb5 +protocol (particularly the AS_REQ) to communicate the client's netbios +name (legacy undotted name, <14 chars) + +This is (my guess) to support the userWorkstations field (in user's AD record). +The idea is to support client-address restrictions, as was standard in NT: +The AD authentication server I imagine checks the netbios address against +this userWorkstations value (BTW, the NetLogon server does this, too). + +The checking of this field implies a little of the next question: + +Is a DAL the layer we need? +--------------------------- + +Looking at what we need to pass around, I don't think +the DAL is even the right layer; what we really want +is to create an account-authorization abstraction layer +(e.g., is this account permitted to login to this computer, +at this time?). +Here is how we ended up doing this in Heimdal: + * We created a separate plugin, with this API: + typedef struct hdb_entry_ex { + void *ctx; + hdb_entry entry; + void (*free_entry)(krb5_context, struct hdb_entry_ex *); + } hdb_entry_ex; + + * The void *ctx is a "private pointer," provided by the 'get' method's + hdb_entry_ex retval. The APIs below use the void *ctx so as to find + additional information about the user, not contained in the hdb_entry + structure. Both the provider and the APIs below understand how to cast + the private void *ctx pointer. + + typedef krb5_error_code + (*krb5plugin_windc_pac_generate)(void *, krb5_context, + struct hdb_entry_ex *, krb5_pac*); + typedef krb5_error_code + (*krb5plugin_windc_pac_verify)(void *, krb5_context, + const krb5_principal, + struct hdb_entry_ex *, + struct hdb_entry_ex *, + krb5_pac *); + typedef krb5_error_code + (*krb5plugin_windc_client_access)(void *, + krb5_context, + struct hdb_entry_ex *, + KDC_REQ *, krb5_data *); + + * (The krb5_data* here is critical, so that samba's KDC can return + the right NTSTATUS code in the 'error string' returned to the client. + Otherwise, the windows client won't get the right error message to + the user (such as 'password expired' etc). The pure Kerberos error + is not enough) + + typedef struct krb5plugin_windc_ftable { + int minor_version; + krb5_error_code (*init)(krb5_context, void **); + void (*fini)(void *); + rb5plugin_windc_pac_generate pac_generate; + krb5plugin_windc_pac_verify pac_verify; + krb5plugin_windc_client_access client_access; + } krb5plugin_windc_ftable; + This API has some heimdal-specific stuff, that'll have to change when we port the plugin to MIT krb. + * 1st callback (pac_generate) creates an initial PAC from the user's AD record. + * 2nd callback (pac_verify) check that a PAC is correctly signed, add additional groups (for cross-realm tickets) and re-sign with the key of the target kerberos service's account + * 3rd callback (client_access) perform additional access checks, such as allowedWorkstations and account expiry. + * for example, to register this plugin, use the kdc's standard + plugin-system at Samba4's initialisation: + /* first, setup the table of callback pointers */ + /* Registar WinDC hooks */ + ret = krb5_plugin_register(krb5_context, + PLUGIN_TYPE_DATA, "windc", + &windc_plugin_table); + /* once registered, the KDC will invoke the callbacks */ + /* while preparing each new ticket (TGT or app-tkt) */ + * an alternate way to register the plugin is with a config-file that names + a DSO (Dynamically Shared Object). + + +This plugin helps bridge an important gap: The user's AD record is much +richer than the Heimdal HDB format allows, so we do AD-specific access +control checks in an AD-specific layer (ie, the plugin), not in the +DB-agnostic KDC server. + +In Novell's pure DAL approach, the DAL only read in the principalName as +the key, so it had trouble performing access-control decisions on things +other than the name (like the addresses). + +There is another, currently unhandled challenge in this area - the need to handle +bad password counts (and good password notification), so that a single policy can +be applied against all means of checking a password (NTLM, Kerberos, LDAP Simple +bind etc) + +The Original work by Novell in creating a DAL did not seem to provide a way to +update the PW counts information. Nevertheless, we know that this is very much +required (and may have been addressed in Simo's subsequent IPA-KDC design), +because in Samba3+eDirectory, great lengths are taken to update this information. + +GSSAPI layer requirements +------------------------- + +Welcome to the wonderful world of canonicalisation + +The MIT Krb5 libs (including GSSAPI) do not support kinit returning a different +realm to what the client asked for, even just in case differences. + +Heimdal has the same problem, and this too applies to the krb5 layer, not +just gssapi. + +there's two kinds of name-canonicalization that can occur: + * lower-to-upper case conversion, because Windows domain names are + usually in upper case; + * an unrecognizable subsitution of names, such as might happen when + a user requests a ticket for a NetBIOS domain name, but gets back + a ticket for the corresponging FQDN. + +As developers, we should test if the AD KDC's name-canonicalisation +can be turned off with the KDCOption flags in the AS-REQ or TGS-REQ; +Windows clients always send the Canonicalize flags as KDCOption values. + +Old Clients (samba3 and HPUX clients) use 'selfmade' gssapi/krb5 tokens +for use in the CIFS session setup. these hand-crafted ASN.1 packets don't +follow rfc1964 perfectly, so server-side krblib code has to be flexible +enough to accept these bent tokens. +It turns out that Windows' GSSAPI server-side code is sloppy about checking +some GSSAPI tokens' checksums. During initial work to implement an AD client, +it was easier to make an acceptable solution (to Windows servers) than to +correctly implement the GSSAPI specification, particularly on top of the +(inflexible) MIT Kerberos API. It did not seem possible to write a correct, +separate GSSAPI implementation on top of MIT Kerberos's public krb5lib API, +and at the time, the effort did not need to extend beyond what Windows would +require. + +The upshot is that old Samba3 clients send GSSAPI tokens bearing incorrect +checksums, which AD's Krb5lib cheerfully accepts (but accepts the good checksums, +too). Similarly, Samba4's heimdal krb5lib accepts these incorrect checksums. +Accordingly, if MIT's krb5lib wants to interoperate with the old Samba3 clients, +then MIT's library will have to do the same. + +Because these old clients use krb5_mk_req() +the app-servers get a chksum field depending on the encryption type, but that's +wrong for GSSAPI (see rfc 1964 section 1.1.1). The Checksum type 8003 should +be used in the Authenticator of the AP-REQ! That (correct use of the 8003 type) +would allows the channel bindings, the GCC_C_* req_flags and optional delegation +tickets to be passed from the client to the server. However windows doesn't +seem to care whether the checksum is of the wrong type, and for CIFS SessionSetups, +it seems that the req_flags are just set to 0. +This deviant checksum can't work for LDAP connections with sign or seal, or +for any DCERPC connection, because those connections do not require the +negotiation of GSS-Wrap paraemters (signing or sealing of whole payloads). +Note: CIFS has an independent SMB signing mechanism, using the Kerberos key. + +see heimdal/lib/gssapi/krb5/accept_sec_context.c, lines 390-450 or so. + +This bug-compatibility is likely to be controversial in the kerberos community, +but a similar need for bug-compatibility arose around MIT's & Heimdal's both +failing to support TGS_SUBKEYs correctly, and there are numerous other cases. +see https://lists.anl.gov/pipermail/ietf-krb-wg/2009-May/007630.html + +So MIT's krb5lib needs to also support old clients! + +Principal Names, long and short names +------------------------------------- + +As far as servicePrincipalNames are concerned, these are not +canonicalised by AD's KDC, except as regards the realm in the reply. +That is, the client gets back the principal it asked for, with +the realm portion 'fixed' to uppercase, long form. +Heimdal doesn't canonicalize names, but Samba4 does some canonicalization: +For hostnames and usernames, Samba4 canonicalizes the requested name only +for the LDAP principal-lookup, but then Samba4 returns the retrieved LDAP +record with the request's original, uncanonicalized hostname replacing the +canonicalized name that actually was retrieved. +AB says that for usernames, Samba4 used to return the canonicalized username, +as retrieved from LDAP. The reason for the different treatment was that +the user needs to present his own canonicalized username to servers, for +ACL-matching. For hostnames this isn't necessary. +So, for bug-compatibility, we may need to optionally disable any +namne-canonicalization that MIT's KDC does. + +The short name of the realm seems to be accepted for at least AS_REQ +operations, but the AD KDC always performs realm-canonicalisation, +which converts the short realm-name to the canonical long form. +So, this causes pain for current krb client libraries. + +The canonicalisation of names matters not only for the KDC, but also +for code that has to deal with keytabs. +With credential-caches, when canonicalization leads to cache-misses, +the client just asks for new credentials for the variant server-name. +This could happen, for example, if the user asks to access the server +twice, using different variants of the server-name. + +We also need to handle type 10 names (NT-ENTERPRISE), which are a full +principal name in the principal field, unrelated to the realm. +The principal field contains both principal & realm names, while the +realm field contains a realm name, too, possibly different. +For example, an NT-ENTERPRISE principal name might look like: +joeblow@microsoft.com@NTDEV.MICROSOFT.COM , +<--principal field-->|<----realm name--->| + +Where joe@microsoft.com is the leading portion, and NTDEV.MICROSOFT.COM is +the realm. This is used for the 'email address-like login-name' feature of AD. + +HOST/ Aliases +------------- + +There is another post somewhere (ref lost for the moment) that details +where in active directory the list of stored aliases for HOST/ is. +This list is read & parsed by the AD KDC, so as to allow any of these +aliased ticket-requests to use the HOST/ key. + +Samba4 currently has set: +sPNMappings: host=ldap,dns,cifs,http (but dns's presence is a bug, somehow) + +AD actually has ~50 entries: + +sPNMappings: host=alerter,appmgmt,cisvc,clipsrv,browser,dhcp,dnscache,replicat + or,eventlog,eventsystem,policyagent,oakley,dmserver,dns,mcsvc,fax,msiserver,i + as,messenger,netlogon,netman,netdde,netddedsm,nmagent,plugplay,protectedstora + ge,rasman,rpclocator,rpc,rpcss,remoteaccess,rsvp,samss,scardsvr,scesrv,seclog + on,scm,dcom,cifs,spooler,snmp,schedule,tapisrv,trksvr,trkwks,ups,time,wins,ww + w,http,w3svc,iisadmin,msdtc + +Domain members that expect the longer list will break in damb4, as of 6/09. +AB says he'll try to fix this right away. + +For example, this is how HTTP/, and CIFS/ can use HOST/ without +any explicit entry in the servicePrincipalName attribute + + +For example, the application-server might have (on its AD record): +servicePrincipalName: HOST/my.computer@MY.REALM + +but the client asks for a ticket to cifs/my.computer@MY.REALM +AD looks in LDAP for both name-variants +AD then transposes cifs -> host after performing the lookup in the +directory (for the original name), then looks for host/my.computer@MY.REALM + +for hostnames & usernames, alternate names appear as extra values in +the multivalued "principal name" attributes: + - For hostnames, the other names (other than it's short name, implied + from the CN), is stored in the servicePrincipalName + - For usernames, the other names are stored in the userPrincipalName + attribute, and can be full e-mail address like names, such as + joe@microsoft.com (see above). + +Jean-Baptiste.Marchand@hsc.fr reminds me: +> This is the SPNMappings attribute in Active Directory: +> http://msdn.microsoft.com/library/en-us/adschema/adschema/a_spnmappings.asp + +We implement this in hdb-ldb. + +Implicit names for Win2000 Accounts +----------------------------------- +AD's records for servers are keyed by CN or by servicePrincipalName, +but for win2k boxes, these records don't include servicePrincipalName, +so, the CN attribute is used instead. +Despite not having a servicePrincipalName on accounts created +by computers running win2000, it appears we are expected +to have an implicit mapping from host/computer.full.name and +host/computer to the computer's entry in the AD LDAP database +(ie, be able to obtain tickets for that host name in the KDC). + +Returned Salt for PreAuthentication +----------------------------------- + +When the KDC replies for pre-authentication, it returns the Salt, +which may be in the form of a principalName that is in no way +connected with the current names. (ie, even if the userPrincipalName +and samAccountName are renamed, the old salt is returned). + +This is the kerberos standard salt, kept in the 'Key'. The +AD generation rules are found in a Mail from Luke Howard dated +10 Nov 2004. The MIT glue layer doesn't really need to care about +these salt-handling details; the samba4 code & the LDAP backend +will conspire to make sure that MIT's KDC gets correct salts. + + +From: Luke Howard <lukeh@padl.com> +Organization: PADL Software Pty Ltd +To: lukeh@padl.com +Date: Wed, 10 Nov 2004 13:31:21 +1100 +Cc: huaraz@moeller.plus.com, samba-technical@lists.samba.org +Subject: Re: Samba-3.0.7-1.3E Active Directory Issues +------- + +Did some more testing, it appears the behaviour has another +explanation. It appears that the standard Kerberos password salt +algorithm is applied in Windows 2003, just that the source principal +name is different. + +Here is what I've been able to deduce from creating a bunch of +different accounts: +[SAM name in this mail means the AD attribute samAccountName . + E.g., jbob for a user and jbcomputer$ for a computer.] + +[UPN is the AD userPrincipalName attribute. For example, jbob@mydomain.com] + +Type of account Principal for Salting +======================================================================== +Computer Account host/<SAM-Name-Without-$>.realm@REALM +User Account Without UPN <SAM-Name>@REALM +User Account With UPN <LHS-Of-UPN>@REALM + +Note that if the computer account's SAM account name does not include +the trailing '$', then the entire SAM account name is used as input to +the salting principal. Setting a UPN for a computer account has no +effect. + +It seems to me odd that the RHS of the UPN is not used in the salting +principal. For example, a user with UPN foo@mydomain.com in the realm +MYREALM.COM would have a salt of MYREALM.COMfoo. Perhaps this is to +allow a user's UPN suffix to be changed without changing the salt. And +perhaps using the UPN for salting signifies a move away SAM names and +their associated constraints. + +For more information on how UPNs relate to the Kerberos protocol, +see: + +http://www.ietf.org/proceedings/01dec/I-D/draft-ietf-krb-wg-kerberos-referrals-02.txt + +-- Luke + + + +Heimdal oddities +---------------- + +Heimdal is built such that it should be able to serve multiple realms +at the same time. This isn't relevant for Samba's use, but it shows +up in a lot of generalisations throughout the code. + +Samba4's code originally tried internally to make it possible to use +Heimdal's multi-realms-per-KDC ability, but this was ill-conceived, +and AB has recently (6/09) ripped the last of that multi-realms +stuff out of samba4. AB says that in AD, it's not really possible +to make this work; several AD components structurally assume that +there's one realm per KDC. However, we do use this to support +canonicalization of realm-names: case variations, plus long-vs-short +variants of realm-names. + +Other odd things: + - Heimdal supports multiple passwords on a client account: Samba4 + seems to call hdb_next_enctype2key() in the pre-authentication + routines to allow multiple passwords per account in krb5. + (I think this was intended to allow multiple salts). + AD doesn't support this, so the MIT port shouldn't bother with + this. + +State Machine safety when using Kerberos and GSSAPI libraries +------------------------------------------------------------- + +Samba's client-side & app-server-side libraries are built on a giant +state machine, and as such have very different +requirements to those traditionally expressed for kerberos and GSSAPI +libraries. + +Samba requires all of the libraries it uses to be state machine safe in +their use of internal data. This does not mean thread safe, and an +application could be thread safe, but not state machine safe (if it +instead used thread-local variables). + +So, what does it mean for a library to be state machine safe? This is +mostly a question of context, and how the library manages whatever +internal state machines it has. If the library uses a context +variable, passed in by the caller, which contains all the information +about the current state of the library, then it is safe. An example +of this state is the sequence number and session keys for an ongoing +encrypted session). + +The other issue affecting state machines is 'blocking' (waiting for a +read on a network socket). Samba's non-blocking I/O doesn't like +waiting for libkrb5 to go away for awhile to talk to the KDC. + +Samba4 provides a hook 'send_to_kdc', that allows Samba4 to take over the +IO handling, and run other events in the meantime. This uses a +'nested event context' (which presents the challenges that the kerberos +library might be called again, while still in the send_to_kdc hook). + +Heimdal has this 'state machine safety' in parts, and we have modified +the lorikeet branch to improve this behviour, when using a new, +non-standard API to tunnelling a ccache (containing a set of tickets) +through the gssapi, by temporarily casting the ccache pointer to a +gss credential pointer. +This new API is Heimdal's samba4-requested gss_krb5_import_cred() fcn; +this will have to be rewritten or ported in the MIT port. + +This replaces an older scheme using the KRB5_CCACHE +environment variable to get the same job done. This tunnelling trick +enables a command-line app-client to run kinit tacitly, before running +GSSAPI for service-authentication. This tunnelling trick avoids the +more usual approach of keeping the ccache pointer in a global variable. + +No longer true; the krb5_context global is gone now: +[Heimdal uses a per-context variable for the 'krb5_auth_context', which +controls the ongoing encrypted connection, but does use global +variables for the ubiquitous krb5_context parameter.] + +The modification that has added most to 'state machine safety' of +GSSAPI is the addition of the gss_krb5_acquire_creds() function. This +allows the caller to specify a keytab and ccache, for use by the +GSSAPI code. Therefore there is no need to use global variables to +communicate this information about keytab & ccache. + +At a more theoritical level (simply counting static and global +variables) Heimdal is not state machine safe for the GSSAPI layer. +(Heimdal is now (6/09) much more nearly free of globals.) +The Krb5 layer alone is much closer, as far as I can tell, blocking +excepted. . + + +As an alternate to fixing MIT Kerberos for better safety in this area, +a new design might be implemented in Samba, where blocking read/write +is made to the KDC in another (fork()ed) child process, and the results +passed back to the parent process for use in other non-blocking operations. + +To deal with blocking, we could have a fork()ed child per context, +using the 'GSSAPI export context' function to transfer +the GSSAPI state back into the main code for the wrap()/unwrap() part +of the operation. This will still hit issues of static storage (one +gss_krb5_context per process, and multiple GSSAPI encrypted sessions +at a time) but these may not matter in practice. + +This approach has long been controversial in the Samba team. +An alternate way would be to be implement E_AGAIN in libkrb5: similar +to the way to way read() works with incomplete operations. to do this +in libkrb5 would be difficult, but valuable. + +In the short-term, we deal with blocking by taking over the network +send() and recv() functions, therefore making them 'semi-async'. This +doesn't apply to DNS yet.These thread-safety context-variables will +probably present porting problems, during the MIT port. This will +probably be most of the work in the port to MIT. + + + +GSSAPI and Kerberos extensions +------------------------------ + +This is a general list of the other extensions we have made to / need from +the kerberos libraries + + - DCE_STYLE : Microsoft's hard-coded 3-msg Challenge/Response handshake + emulates DCE's preference for C/R. Microsoft calls this DCE_STYLE. + MIT already has this nowadays (6/09). + + - gsskrb5_get_initiator_subkey() (return the exact key that Samba3 + has always asked for. gsskrb5_get_subkey() might do what we need + anyway). This is necessary, because in some spots, Microsoft uses + raw Kerberos keys, outside the Kerberos protocls, and not using Kerberos + wrappings etc. Ie, as a direct input to MD5 and ARCFOUR, without using + the make_priv() or make_safe() calls. + + - gsskrb5_acquire_creds() (takes keytab and/or ccache as input + parameters, see keytab and state machine discussion in prev section) + +Not needed anymore, because MIT's code now handles PACs fully: + - gss_krb5_copy_service_keyblock() (get the key used to actually + encrypt the ticket to the server, because the same key is used for + the PAC validation). + - gsskrb5_extract_authtime_from_sec_context (get authtime from + kerberos ticket) + - gsskrb5_extract_authz_data_from_sec_context (get authdata from + ticket, ie the PAC. Must unwrap the data if in an AD-IFRELEVENT)] +The new function to handle the PAC fully + - gsskrb5_extract_authz_data_from_sec_context() + +Samba still needs this one: + - gsskrb5_wrap_size (find out how big the wrapped packet will be, + given input length). + +Keytab requirements +------------------- + +Because windows machine account handling is very different to the +traditional 'MIT' keytab operation. +This starts when we look at the basics of the secrets handling: + +Samba file-servers can have many server-name simultaneously (kindof +like web servers' software virtual hosting), but since these servers +are running in AD, these names are free to be set up to all share +the same secret key. In AD, host-sharing server names almost always +share a secret key like this. In samba3, this key-sharing was optional, so +some samba3 hosts' keytabs did hold multiple keys. samba4 abandons this +traditional "old MIT" style of keytab, and only supports one key per keytab, +and multiple server-names can use that keytab key in common. +Heimdal offered "in-memory keytabs" for servers that use passwords. +These server-side passwords were held in a Samba LDB database called secrets.ldb, +and the heimdal library would be supplied the password from the ldb file and +would construct an in-memory keytab struct containing the password, +just as if the library had read an MIT-style keytab file. +Unfortunately, only later, at recv_auth() time, would the heimdal library +convert the PW into a salted-&-hashed AES key, by hashing 10,000 times with +SHA-1. So, nowadays, this password-based in-memory keytab is seen as too +slow, and is falling into disuse. + +Traditional 'MIT' behaviour is to use a keytab, containing salted key +data, extracted from the KDC. (In this model, there is no 'service +password', instead the keys are often simply application of random +bytes). Heimdal also implements this behaviour. + +The windows model is very different - instead of sharing a keytab with +each member server, a random utf-16 pseudo-textual password is stored +for the whole machine. +The password is set with non-kerberos mechanisms (particularly SAMR, +a DCE-RPC service) and when interacting on a kerberos basis, the +password is salted by the member server (ie, an AD server-host). +(That is, no salt information appears to be conveyed from the AD KDC +to the member server. ie, the member server must use the rule's +described in Luke's mail above). + +pre-win7 AD and samba3/4 both use SAMR, an older protocol, to jumpstart +the member server's PW-sharing with AD (the "windows domain-join process"). +This PW-sharing transfers only the PW's utf-16 text, without any salting +or hashing, so that non-krb security mechanisms can use the same utf-16 +text PW. for windows 7, this domain-joining uses LDAP for PW-setting. + +In dealing with this model, we use both the traditional file +keytab and in-MEMORY keytabs. + +When dealing with a windows KDC, the behaviour regarding case +sensitivity and canonacolisation must be accomidated. This means that +an incoming request to a member server may have a wide variety of +service principal names. These include: + +machine$@REALM (samba clients) +HOST/foo.bar@realm (win2k clients) +HOST/foo@realm (win2k clients, using netbios) +cifs/foo.bar@realm (winxp clients) +cifs/foo@realm (winxp clients, using netbios) + +as well as all case variations on the above. + +Heimdal's GSSAPI expects to get a principal-name & a keytab, possibly containing +multiple principals' different keys. However, AD has a different problem to +solve, which is that the client may know the member-server by a non-canonicalized +principal name, yet AD knows the keytab contains exactly one key, indexed by +the canonical name. So, GSSAPI is unprepared to canonicalize the server-name +that the cliet requested, and is also overprepared to do an unnecessary search +through the keytab by principal-name. So samba's server-side GSSAPI calls game +the GSSAPI, by supplying the server's known canonical name, and the one-key keytab. +this doesn't really affect the port to mit-krb. + +Because the number of U/L case combinations got 'too hard' to put into a keytab in the +traditional way (with the client to specify the name), we either +pre-compute the keys into a traditional keytab or make an in-MEMORY +keytab at run time. In both cases we specify the principal name to +GSSAPI, which avoids the need to store duplicate principals. + +We use a 'private' keytab in our private dir, referenced from the +secrets.ldb by default. + +Extra Heimdal functions used +---------------------------- +these fcns didn't exist in the MIT code, years ago, when samba started. +AB will try to build a final list of these fcns. + +(an attempt to list some of the Heimdal-specific functions I know we use) + +krb5_free_keyblock_contents() + +also a raft of prinicpal manipulation functions: + +Prncipal Manipulation +--------------------- + +Samba makes extensive use of the principal manipulation functions in +Heimdal, including the known structure behind krb_principal and +krb5_realm (a char *). for example, +krb5_parse_name_flags(smb_krb5_context->krb5_context, name, + KRB5_PRINCIPAL_PARSE_MUST_REALM, &principal); +krb5_princ_realm(smb_krb5_context->krb5_context, principal); +krb5_unparse_name_flags(smb_krb5_context->krb5_context, principal, + KRB5_PRINCIPAL_UNPARSE_NO_REALM, &new_princ); +These are needed for juggling the AD variant-structures for server names. + +Authz data extraction +--------------------- + +We use krb5_ticket_get_authorization_data_type(), and expect it to +return the correct authz data, even if wrapped in an AD-IFRELEVENT container. + +KDC/hdb Extensions +-------------- + +We have modified Heimdal's 'hdb' interface to specify the 'class' of +Principal being requested. This allows us to correctly behave with +the different 'classes' of Principal name. This is necessary because +of the AD structure, which uses very different record-structures +for user-principals, trust principals & server-principals. + +We currently define 3 classes: + - client (kinit) + - server (tgt) + - krbtgt (kinit, tgt) the kdc's own ldap record + +I also now specify the kerberos principal as an explict parameter to LDB_fetch(), +not an in/out value on the struct hdb_entry parameter itself. + +Private Data pointer (and windc hooks) (see above): + In addition, I have added a new interface hdb_fetch_ex(), which + returns a structure including a private data-pointer, which may be used + by the windc plugin inferface functions. The windc plugin provides + the hook for the PAC, as well as a function for the main access control routines. + + A new windc plugin function should be added to increment the bad password counter + on failure. + +libkdc (doesn't matter for IPA; Samba invokes the Heimdal kdc as a library call, +but this is just a convenience, and the MIT port can do otherwise w/o trouble.) +------ + +Samba4 needs to be built as a single binary (design requirement), and +this should include the KDC. Samba also (and perhaps more +importantly) needs to control the configuration environment of the +KDC. + +The interface we have defined for libkdc allow for packet injection +into the post-socket layer, with a defined krb5_context and +kdb5_kdc_configuration structure. These effectively redirect the +kerberos warnings, logging and database calls as we require. + +Using our socket lib (para 3 does matter for the send_to_kdc() plugin). +See also the discussion about state machine safety above) +-------------------- + +An important detail in the use of libkdc is that we use samba4's own socket +lib. This allows the KDC code to be as portable as the rest of samba +(this cuts both ways), but far more importantly it ensures a +consistancy in the handling of requests, binding to sockets etc. + +To handle TCP, we use of our socket layer in much the same way as +we deal with TCP for CIFS. Tridge created a generic packet handling +layer for this. + +For the client, samba4 likewise must take over the socket functions, +so that our single thread smbd will not lock up talking to itself. +(We allow processing while waiting for packets in our socket routines). +send_to_kdc() presents to its caller the samba-style socket interface, +but the MIT port will reimplement send_to_kdc(), and this routine will +use internally the same socket library that MIT-krb uses. + +Kerberos logging support (this will require porting attention) +------------------------ + +Samba4 now (optionally in the main code, required for the KDC) uses the +krb5_log_facility from Heimdal. This allows us to redirect the +warnings and status from the KDC (and client/server kerberos code) to +Samba's DEBUG() system. + +Similarly important is the Heimdal-specific krb5_get_error_string() +function, which does a lot to reduce the 'administrator pain' level, +by providing specific, english text-string error messages instead of +just error code translations. (this isn't necessarty for the port, +but it's more useful than MIT's default err-handling; make sure +this works for MIT-krb) + + +Short name rules +---------------- + +Samba is highly likely to be misconfigured, in many weird and +interesting ways. As such, we have a patch for Heimdal that avoids +DNS lookups on names without a . in them. This should avoid some +delay and root server load. (this may need to be ported to MIT.) + +PAC Correctness +--------------- + +We now put the PAC into the TGT, not just the service ticket. + +Forwarded tickets +----------------- + +We extract forwarded tickets from the GSSAPI layer, and put +them into the memory-based credentials cache. +We can then use them for proxy work. + + +Kerberos TODO +============= + +(Feel free to contribute to any of these tasks, or ask +abartlet@samba.org about them). + +Lockout Control (still undone in samba4 on heimdal) +-------------- + +We need to get (either if PADL publishes their patch, or write our +own) access control hooks in the Heimdal KDC. We need to lockout +accounts (eg, after 10 failed PW-attemps), and perform other controls. +This is standard AD behavior, that samba4 needs to get right, whether +heimdal or MIT-krb is doing the ticket work. + +Gssmonger +--------- + +Microsoft has released a krb-specific testsuite called gssmonger, +which tests interop. We should compile it against lorikeet-heimdal, +MIT and see if we can build a 'Samba4' server for it. +GSSMonger wasn't intended to be Windows-specific. + +Kpasswd server (kpasswd server is now finished, but not testsuite) +-------------- + +I have a partial kpasswd server which needs finishing, and a we need a +client testsuite written, either via the krb5 API or directly against +GENSEC and the ASN.1 routines. +Samba4 likes to test failure-modes, not just successful behavior. + +Currently it only works for Heimdal, not MIT clients. This may be due +to call ordering constraints. + + +Correct TCP support +------------------- + +Samba4 socket-library's current TCP support does not send back 'too large' +error messages if the high bit is set. This is needed for a proposed extension +mechanism (SSL-armored kinit, by Leif Johansson <leifj@it.su.se>), +but is likewise unsupported in both current Heimdal and MIT. + +========================================================================= +AB says MIT's 1.7 announcement about AD support covers Luke Howard's +changes. It all should be easy for IPA to exploit/use during the port +of Samba4 to MIT. +AB says Likewise software will likely give us their freeware NTLM/MIT-krb +implementation. diff --git a/source4/auth/kerberos/kerberos-porting-to-mit-notes.txt b/source4/auth/kerberos/kerberos-porting-to-mit-notes.txt new file mode 100644 index 0000000..9b478bb --- /dev/null +++ b/source4/auth/kerberos/kerberos-porting-to-mit-notes.txt @@ -0,0 +1,803 @@ +Copyright Andrew Bartlett <abartlet@samba.org> 2005-2009 +Copyright Donald T. Davis <don@mit.edu> 2009 + +Released under the GPLv3 +"Porting Samba4 to MIT-Krb" + + + From Idmwiki + + +IPA v3 will use a version of Samba4 built on top of MIT's Kerberos +implementation, instead of Heimdal's version of Kerberos. + +Task list summary for porting changes needed, from Andrew Bartlett: + + * Rewrite or extend the LDAP driver that MIT-KDC will use. + * MIT KDC changes: rewrite DAL, add TGS-KBAC, enable PACs,... + * Full thread-safety for MIT's library code, + * Many small changes + +Task list, without explanations (the list with explanations is in the +later sections of this document): + +Porting Samba4 to MIT-krb comprises four main chunks of work: + 1. Rewrite or extend the LDAP driver that MIT-KDC will use: + a. Our LDAP driver for the KDB needs to know how to do + Samba4's intricate canonicalization of server names, + user-names, and realm names. + b. AD-style aliases for HOST/ service names. + c. Implicit names for Win2k accounts. + d. Principal "types": client / server / krbtgs + e. Most or all of this code is in 3 source files, + ~1000 lines in all; + 2. MIT KDC changes + a. Rewrite the MIT KDC's Data-Abstraction Layer (DAL), + mostly because he MIT KDC needs to see& manipulate + more LDAP detail, on Samba4's behalf; + b. Add HBAC to the KDC's TGT-issuance, so that Samba4 + can refuse TGTs to kinit, based on time-of-day& + IP-addr constraints; + c. turn on MIT-krb 1.7's PAC handling + d. add bad-password counts, for unified account-lockouts + across all authT methods (Krb, NTLM, LDAP simple bind, + etc) + 3. Make sure MIT's library code is more fully thread-safe, + by replacing all global and static variables with context + parameters for the library routines. This may already be + done. + 4. Many small changes (~15) + a. some extensions to MIT's libkrb5& GSSAPI libraries, + including GSSAPI ticket-forwarding + b. some refitting in Samba4's use of the MIT libraries; + c. make sure Samba4's portable socket API works, + including "packet too large" errors; + d. MIT's GSSAPI code should support some legacy Samba3 + clients that present incorrectly-calculated checksums; + e. Samba4 app-server-host holds aUTF-16 PW, plus a + key bitstring; + f. in-memory-only credentials cache; + g. in-memory-only keytab (nice to have); + h. get OSS NTLM authT library (Likewise Software?); + i. special Heimdal-specific functions; + j. principal-manipulation functions; + k. special check for misconfigured Samba4 hostnames; + l. improved krb error-messages; + m. improved krb logging + n. MS GSSMonger test-suite + o. testsuite for kpasswd daemon + +0. Introduction: This document should be read alongside the Samba4 +source code, as follows: + + * For DAL and KDC requirements, please see Samba4's + source4/kdc/hdb-samba4.c in particular. This file + is an implementation against Heimdal's HDB abstraction + layer, and is the biggest part of the samba-to-krb + glue layer, so the main part of the port to MIT is + to replace hdb-samba4 with a similar glue layer + that's designed for MIT's code. + * Samba4's PAC requirements are implemeneted in + source4/kdc/pac-glue.c + * Both of the above two layers are Heimdal plugins, and + both get loaded in source4/kdc/kdc.c + * For GSSAPI requirements, see auth/gensec/gensec_gssapi.c + (the consumer of GSSAPI in Samba4) + * For Kerberos library requirements, see + auth/kerberos/krb5_init_context.c + * Samba has its own credentials system, wrapping GSS creds, + just as GSS creds wrap around krb5 creds. For the + interaction between Samba4 credential system and GSSAPI + and Kerberos, see auth/credentials/credentials_krb5. + +1. Rewrite or extend the LDAP driver that MIT-KDC will use. + + a. IPA'sLDAP driver for the KDB needs to know how to do + Samba4's intricate canonicalization of server names, + user-names, and realm names. + For hostnames& usernames, alternate names appear in + LDAP as extra values in the multivalued "principal name" + attributes: + * For a hostname, the alternate names (other than + the short name, implied from the CN), are stored in + the servicePrincipalName + * For a username, the alternate names are stored in + the userPrincipalName attribute, and can be long + email-address-like names, such as joe@microsoft.com + (see "Type 10 names," below). + GSSAPI layer requirements: Welcome to the wonderful + world of canonicalisation. The MIT Krb5 libs (including + GSSAPI) do not enable the AS to send kinit a TGT containing + a different realm-name than what the client asked for, + even in U/L case differences. Heimdal has the same problem, + and this applies to the krb5 layer too, not just GSSAPI. + There are two kinds of name-canonicalization that can + occur on Windows: + * Lower-to-upper case conversion, because Windows domain + names are usually in upper case; + * An unrecognizable subsitution of names, such as might + happen when a user requests a ticket for a NetBIOS domain + name, but gets back a ticket for the corresponging FQDN. + As developers, we should test if the AD KDC's name-canonical- + isation can be turned off with the KDCOption flags in the + AS-REQ or TGS-REQ; Windows clients always send the + Canonicalize flags as KDCOption values. + Principal Names, long and short names: + AD's KDC does not canonicalize servicePrincipalNames, except + for the realm in the KDC reply. That is, the client gets + back the principal it asked for, with the realm portion + 'fixed' to uppercase, long form. + Samba4 does some canonicalization, though Heimdal doesn't + canonicalize names itself: For hostnames and usernames, + Samba4 canonicalizes the requested name only for the LDAP + principal-lookup, but then Samba4 returns the retrieved LDAP + record with the request's original, uncanonicalized hostname + replacing the canonicalized name that actually was found. + Usernames: AndrewB says that Samba4 used to return + the canonicalized username exactly as retrieved from LDAP. + The reason Samba4 treated usernames differently was that + the user needs to present his own canonicalized username + to servers, for ACL-matching. For hostnames this isn't + necessary. + Realm-names: AD seems to accept a realm's short name + in krb-requests, at least for AS_REQ operations, but the + AD KDC always performs realm-canonicalisation, which + converts the short realm-name to the canonical long form. + So, this causes pain for current krb client libraries. + Punchline: For bug-compatibility, we may need to + selectively or optionally disable the MIT-KDC's name- + canonicalization. + Application-code: + Name-canonicalisation matters not only for the KDC, but + also for app-server-code that has to deal with keytabs. + Further, with credential-caches, canonicalization can + lead to cache-misses, but then the client just asks for + new credentials for the variant server-name. This could + happen, for example, if the user asks to access the + server twice, using different variants of the server-name. + Doubled realm-names: We also need to handle type 10 + names (NT-ENTERPRISE), which are a full principal name + in the principal field, unrelated to the realm. The + principal field contains both principal& realm names, + while the realm field contains a realm name, too, possibly + different. For example, an NT-ENTERPRISE principal name + might look like: joeblow@microsoft.com@NTDEV.MICROSOFT.COM , + <--principal field-->|<----realm name--->| + Where joe@microsoft.com is the leading portion, and + NTDEV.MICROSOFT.COM is the realm. This is used for the + 'email address-like login-name' feature of AD. + b.AD-style aliases for HOST/ service names. + AD keeps a list of service-prefixed aliases for the host's + principal name. The AD KDC reads& parses this list, so + as to allow the aliased services to share the HOST/ key. + This means that every ticket-request for a service-alias + gets a service-ticket encrypted in the HOST/ key. + For example, this is how HTTP/ and CIFS/ can use the + HOST/ AD-LDAP entry, without any explicitly CIFS-prefixed + entry in the host's servicePrincipalName attribute. In the + app-server host's AD record, the servicePrincipalName says + only HOST/my.computer@MY.REALM , but the client asks + for CIFS/my.omputer@MY.REALM tickets. So, AD looks in + LDAP for both name-variants, and finds the HOST/ version, + In AD's reply, AD replaces the HOST/ prefix with CIFS/ . + We implement this in hdb-ldb. + (TBD: Andrew, is this correct?:) + List of HOST/ aliases: Samba4 currently uses only a small + set of HOST/ aliases: sPNMappings: host=ldap,dns,cifs,http . + Also, dns's presence in this list is a bug, somehow. + AD's real list has 53 entries: + sPNMappings: host=alerter,appmgmt,cisvc,clipsrv,browser, + dhcp,dnscache,replicator,eventlog,eventsystem,policyagent, + oakley,dmserver,dns,mcsvc,fax,msiserver,ias,messenger, + netlogon,netman,netdde,netddedsm,nmagent,plugplay, + protectedstorage,rasman,rpclocator,rpc,rpcss,remoteaccess, + rsvp,samss,scardsvr,scesrv,seclogon,scm,dcom,cifs,spooler, + snmp,schedule,tapisrv,trksvr,trkwks,ups,time,wins,www, + http,w3svc,iisadmin,msdtc + Domain members that expect the longer list will break in + Samba4, as of 6/09. AB says he'll try to fix this right + away. There is another post somewhere (ref lost for the + moment) that details where in active directory the long + list of stored aliases for HOST/ is. + c.Implicit names for Win2000 Accounts: AD keys its + server-records by CN or by servicePrincipalName, but a + win2k box's server-entry in LDAP doesn't include the + servicePrincipalName attribute, So, win2k server-accounts + are keyed by the CN attribute instead. Because AD's LDAP + doesn't have a servicePrincipalName for win2k servers' + entries, Samba4 has to have an implicit mapping from + host/computer.full.name and from host/computer, to the + computer's CN-keyed entry in the AD LDAP database, so to + be able to find the win2k server's host name in the KDB. + d.Principal "types": + We have modified Heimdal's 'hdb' interface to specify + the 'class' of Principal being requested. This allows + us to correctly behave with the different 'classes' of + Principal name. This is necessary because of AD's LDAP + structure, which uses very different record-structures + for user-principals, trust principals& server-principals. + We currently define 3 classes: + * client (kinit) + * server (tgt) + * krbtgt the TGS's own ldap record + Samba4 also now specifies the kerberos principal as an + explicit parameter to LDB_fetch(), not an in/out value + on the struct hdb_entry parameter itself. + e. Most or all of this LDAP driver code is in three source + files, ~1000 lines in all. These files are in + samba4/kdc : + * hdb-samba4.c (samba4-to-kdb glue-layer plugin) + * pac-glue.c (samba4's pac glue-layer plugin) + * kdc.c (loads the above two plugins). + +2. MIT KDC changes + + a.Data-Abstraction Layer (DAL): It would be good to + rewrite or circumvent the MIT KDC's DAL, mostly because + the MIT KDC needs to see& manipulate more LDAP detail, + on Samba4's behalf. AB says the MIT DAL may serve well- + enough, though, mostly as is. AB says Samba4 will need + the private pointer part of the KDC plugin API, though, + or the PAC generation won't work (see sec.2.c, below). + * MIT's DAL calls lack context parameters (as of 2006), + so presumably they rely instead on global storage, and + aren't fully thread-safe. + * In Novell's pure DAL approach, the DAL only read in the + principalName as the key, so it had trouble performing + access-control decisions on things other than the user's + name (like the addresses). + * Here's why Samba4 needs more entry detail than the DAL + provides: The AS needs to have ACL rules that will allow + a TGT to a user only when the user logs in from the + right desktop addresses, and at the right times of day. + This coarse-granularity access-control could be enforced + directly by the KDC's LDAP driver, without Samba having + to see the entry's pertinent authZ attributes. But, + there's a notable exception: a user whose TGT has + expired, and who wants to change his password, should + be allowed a restricted-use TGT that gives him access + to the kpasswd service. This ACL-logic could be buried + in the LDAP driver, in the same way as the TGS ACL could + be enforced down there, but to do so would just be even + uglier than it was to put the TGS's ACL-logic in the driver. + * Yet another complaint is that the DAL always pulls an + entire LDAP entry, non-selectively. The current DAL + is OK for Samba4's purposes, because Samba4 only reads, + and doesn't write, the KDB. But this all-or-nothing + retrieval hurts the KDC's performance, and would do so + even more, if Samba had to use the DAL to change KDB + entries. + b.Add HBAC to the KDC's TGT-issuance, so that Samba4 can + refuse TGTs to kinit, based on time-of-day& IP-address + constraints. AB asks, "Is a DAL the layer we need?" + Looking at what we need to pass around, AB doesn't think + the DAL is the right layer; what we really want instead + is to create an account-authorization abstraction layer + (e.g., is this account permitted to login to this computer, + at this time?). Samba4 ended up doing account-authorization + inside Heimdal, via a specialized KDC plugin. For a summary + description of this plugin API, see Appendix 2. + c. Turn on MIT-krb 1.7'sPAC handling. + In addition, I have added a new interface hdb_fetch_ex(), + which returns a structure including a private data-pointer, + which may be used by the windc plugin inferface functions. + The windc plugin provides the hook for the PAC. + d. Samba4 needsaccess control hooks in the Heimdal& MIT + KDCs. We need to lockout accounts (eg, after 10 failed PW- + attemps), and perform other controls. This is standard + AD behavior, that Samba4 needs to get right, whether + Heimdal or MIT-krb is doing the ticket work. + - If PADL doesn't publish their patch for this, + we'll need to write our own. + - The windc plugin proivides a function for the main + access control routines. A new windc plugin function + should be added to increment the bad password counter + on failure. + - Samba4 doesn't yet handle bad password counts (or good + password notification), so that a single policy can be + applied against all means of checking a password (NTLM, + Kerberos, LDAP Simple Bind, etc). Novell's original DAL + did not provide a way to update the PW counts information. + - Nevertheless, we know that this is very much required in + AD, because Samba3 + eDirectory goes to great lengths to + update this information. This may have been addressed in + Simo's subsequent IPA-KDC design), + * AllowedWorkstationNames and Krb5: Microsoft uses the + clientAddresses *multiple value* field in the krb5 + protocol (particularly the AS_REQ) to communicate the + client's netbios name (legacy undotted name,<14 chars) + AB guesses that this is to support the userWorkstations + field (in user's AD record). The idea is to support + client-address restrictions, as was standard in NT: + The AD authentication server probably checks the netbios + address against this userWorkstations value (BTW, the + NetLogon server does this, too). + +3. State Machine safety +when using Kerberos and GSSAPI libraries + + * Samba's client-side& app-server-side libraries are built + on a giant state machine, and as such have very different + requirements to those traditionally expressed for kerberos + and GSSAPI libraries. + * Samba requires all of the libraries it uses to be "state + machine safe" in their use of internal data. This does not + necessarily mean "thread safe," and an application could be + thread safe, but not state machine safe (if it instead used + thread-local variables). so, if MIT's libraries were made + thread-safe only by inserting spinlock() code, then the MIT + libraries aren't yet "state machine safe." + * So, what does it mean for a library to be state machine safe? + This is mostly a question of context, and how the library manages + whatever internal state machines it has. If the library uses a + context variable, passed in by the caller, which contains all + the information about the current state of the library, then it + is safe. An example of this state is the sequence number and + session keys for an ongoing encrypted session). + * The other issue affecting state machines is 'blocking' (waiting for a + read on a network socket). Samba's non-blocking I/O doesn't like + waiting for libkrb5 to go away for awhile to talk to the KDC. + * Samba4 provides a hook 'send_to_kdc', that allows Samba4 to take over the + IO handling, and run other events in the meantime. This uses a + 'nested event context' (which presents the challenges that the kerberos + library might be called again, while still in the send_to_kdc hook). + * Heimdal has this 'state machine safety' in parts, and we have modified + Samba4's lorikeet branch to improve this behaviour, when using a new, + non-standard API to tunnelling a ccache (containing a set of tickets) + through the gssapi, by temporarily casting the ccache pointer to a + gss credential pointer. This new API is Heimdal's samba4-requested + gss_krb5_import_cred() fcn; this will have to be rewritten or ported + in the MIT port. + * This tunnelling trick replaces an older scheme using the KRB5_CCACHE + environment variable to get the same job done. The tunnelling trick + enables a command-line app-client to run kinit tacitly, before running + GSSAPI for service-authentication. The tunnelling trick avoids the + more usual approach of keeping the ccache pointer in a global variable. + * [Heimdal uses a per-context variable for the 'krb5_auth_context', + which controls the ongoing encrypted connection, but does use global + variables for the ubiquitous krb5_context parameter. (No longer true, + because the krb5_context global is gone now.)] + * The modification that has added most to 'state machine safety' of + GSSAPI is the addition of the gss_krb5_acquire_creds() function. + This allows the caller to specify a keytab and ccache, for use by + the GSSAPI code. Therefore there is no need to use global variables + to communicate this information about keytab& ccache. + * At a more theoretical level (simply counting static and global + variables) Heimdal is not state machine safe for the GSSAPI layer. + (But Heimdal is now (6/09) much more nearly free of globals.) + The Krb5 layer alone is much closer, as far as I can tell, blocking + excepted. . + * As an alternate to fixing MIT Kerberos for better safety in this area, + a new design might be implemented in Samba, where blocking read/write + is made to the KDC in another (fork()ed) child process, and the results + passed back to the parent process for use in other non-blocking operations. + * To deal with blocking, we could have a fork()ed child per context, + using the 'GSSAPI export context' function to transfer + the GSSAPI state back into the main code for the wrap()/unwrap() part + of the operation. This will still hit issues of static storage (one + gss_krb5_context per process, and multiple GSSAPI encrypted sessions + at a time) but these may not matter in practice. + * This approach has long been controversial in the Samba team. + An alternate way would be to be implement E_AGAIN in libkrb5: similar + to the way to way read() works with incomplete operations. to do this + in libkrb5 would be difficult, but valuable. + * In the short-term, we deal with blocking by taking over the network + send() and recv() functions, therefore making them 'semi-async'. This + doesn't apply to DNS yet.These thread-safety context-variables will + probably present porting problems, during the MIT port. This will + probably be most of the work in the port to MIT. + This may require more thorough thread-safe-ing work on the MIT libraries. + +4. Many small changes (~15) + + a. Some extensions to MIT'slibkrb5& GSSAPI libraries, including + GSSAPI ticket-forwarding: This is a general list of the other + extensions Samba4 has made to / need from the kerberos libraries + * DCE_STYLE : Microsoft's hard-coded 3-msg Challenge/Response handshake + emulates DCE's preference for C/R. Microsoft calls this DCE_STYLE. + MIT already has this nowadays (6/09). + * gsskrb5_get_initiator_subkey() (return the exact key that Samba3 + has always asked for. gsskrb5_get_subkey() might do what we need + anyway). This routine is necessary, because in some spots, + Microsoft uses raw Kerberos keys, outside the Kerberos protocols, + as a direct input to MD5 and ARCFOUR, without using the make_priv() + or make_safe() calls, and without GSSAPI wrappings etc. + * gsskrb5_acquire_creds() (takes keytab and/or ccache as input + parameters, see keytab and state machine discussion in prev section) + * The new function to handle the PAC fully + gsskrb5_extract_authz_data_from_sec_context() + need to test that MIT's PAC-handling code checks the PAC's signature. + * gsskrb5_wrap_size (Samba still needs this one, for finding out how + big the wrapped packet will be, given input length). + b. Some refitting in Samba4's use of the MIT libraries; + c. Make sure Samba4'sportable socket API works: + * An important detail in the use of libkdc is that we use samba4's + own socket lib. This allows the KDC code to be as portable as + the rest of samba, but more importantly it ensures consistancy + in the handling of requests, binding to sockets etc. + * To handle TCP, we use of our socket layer in much the same way as + we deal with TCP for CIFS. Tridge created a generic packet handling + layer for this. + * For the client, samba4 likewise must take over the socket functions, + so that our single thread smbd will not lock up talking to itself. + (We allow processing while waiting for packets in our socket routines). + send_to_kdc() presents to its caller the samba-style socket interface, + but the MIT port will reimplement send_to_kdc(), and this routine will + use internally the same socket library that MIT-krb uses. + * The interface we have defined for libkdc allows for packet injection + into the post-socket layer, with a defined krb5_context and + kdb5_kdc_configuration structure. These effectively redirect the + kerberos warnings, logging and database calls as we require. + * Samba4 socket-library's current TCP support does not send back + 'too large' error messages if the high bit is set. This is + needed for a proposed extension mechanism (SSL-armored kinit, + by Leif Johansson<leifj@it.su.se>), but is currently unsupported + in both Heimdal and MIT. + d. MIT's GSSAPI code should support some legacy Samba3 + clients that presentincorrectly-calculated checksums. + * Old Clients (samba3 and HPUX clients) use 'selfmade' + gssapi/krb5 tokens for use in the CIFS session setup. + These hand-crafted ASN.1 packets don't follow rfc1964 + (GSSAPI) perfectly, so server-side krblib code has to + be flexible enough to accept these bent tokens. + * It turns out that Windows' GSSAPI server-side code is + sloppy about checking some GSSAPI tokens' checksums. + During initial work to implement an AD client, it was + easier to make an acceptable solution (acceptable to + Windows servers) than to correctly implement the + GSSAPI specification, particularly on top of the + (inflexible) MIT Kerberos API. It did not seem + possible to write a correct, separate GSSAPI + implementation on top of MIT Kerberos's public + krb5lib API, and at the time, the effort did not + need to extend beyond what Windows would require. + * The upshot is that old Samba3 clients send GSSAPI + tokens bearing incorrect checksums, which AD's + GSSAPI library cheerfully accepts (but accepts + the good checksums, too). Similarly, Samba4's + Heimdal krb5lib accepts these incorrect checksums. + Accordingly, if MIT's krb5lib wants to interoperate + with the old Samba3 clients, then MIT's library will + have to do the same. + * Because these old clients use krb5_mk_req() + the app-servers get a chksum field depending on the + encryption type, but that's wrong for GSSAPI (see + rfc 1964 section 1.1.1). The Checksum type 8003 + should be used in the Authenticator of the AP-REQ! + That (correct use of the 8003 type) would allow + the channel bindings, the GCC_C_* req_flags and + optional delegation tickets to be passed from the + client to the server. However windows doesn't seem + to care whether the checksum is of the wrong type, + and for CIFS SessionSetups, it seems that the + req_flags are just set to 0. This deviant checksum + can't work for LDAP connections with sign or seal, + or for any DCERPC connection, because those + connections do not require the negotiation of + GSS-Wrap paraemters (signing or sealing of whole + payloads). Note: CIFS has an independent SMB + signing mechanism, using the Kerberos key. + * For the code that handles the incorrect& correct + checksums, see heimdal/lib/gssapi/krb5/accept_sec_context.c, + lines 390-450 or so. + * This bug-compatibility is likely to be controversial + in the kerberos community, but a similar need for bug- + compatibility arose around MIT's& Heimdal's both + failing to support TGS_SUBKEYs correctly, and there + are numerous other cases. + seehttps://lists.anl.gov/pipermail/ietf-krb-wg/2009-May/007630.html + * So, MIT's krb5lib needs to also support old clients! + e. Samba4 app-server-host holds aUTF-16 PW, plus a key bitstring; + See Appendix 1, "Keytab Requirements." + f.In-memory-only credentials cache for forwarded tickets + Samba4 extracts forwarded tickets from the GSSAPI layer, + and puts them into the memory-based credentials cache. + We can then use them for proxy work. This needs to be + ported, if the MIT library doesn't do it yet. + g.In-memory-only keytab (nice to have): + Heimdal used to offer "in-memory keytabs" for servers that use + passwords. These server-side passwords were held in a Samba LDB + database called secrets.ldb . The heimdal library would fetch + the server's password from the ldb file and would construct an + in-memory keytab struct containing the password, somewhat as if + the library had read an MIT-style keytab file. Unfortunately, + only later, at recv_auth() time, would the Heimdal library convert + the server-PW into a salted-&-hashed AES key, by hashing 10,000 + times with SHA-1. Naturally, this is really too slow for recv_auth(), + which runs when an app-server authenticates a client's app-service- + request. So, nowadays, this password-based in-memory keytab is + falling into disuse. + h. Get OSSNTLM authT library: AB says Likewise software + probably will give us their freeware "NTLM for MIT-krb" + implementation. + i. Special Heimdal-specific functions; These functions didn't + exist in the MIT code, years ago, when Samba started. AB + will try to build a final list of these functions: + * krb5_free_keyblock_contents() + * + j.Principal-manipulation functions: Samba makes extensive + use of the principal manipulation functions in Heimdal, + including the known structure behind krb_principal and + krb5_realm (a char *). For example, + * krb5_parse_name_flags(smb_krb5_context->krb5_context, name, + KRB5_PRINCIPAL_PARSE_REQUIRE_REALM,&principal); + * krb5_unparse_name_flags(smb_krb5_context->krb5_context, principal, + KRB5_PRINCIPAL_UNPARSE_NO_REALM,&new_princ); + * krb5_principal_get_realm() + * krb5_principal_set_realm() + These are needed for juggling the AD variant-structures + for server names. + k. SpecialShort name rules check for misconfigured Samba4 + hostnames; Samba is highly likely to be misconfigured, in + many weird and interesting ways. So, we have a patch for + Heimdal that avoids DNS lookups on names without a "." in + them. This should avoid some delay and root server load. + (This errors need to be caught in MIT's library.) + l.Improved krb error-messages; + krb5_get_error_string(): This Heimdal-specific function + does a lot to reduce the 'administrator pain' level, by + providing specific, English text-string error messages + instead of just error code translations. (This isn't + necessary for the port, but it's more useful than MIT's + default err-handling; Make sure this works for MIT-krb) + m.Improved Kerberos logging support: + krb5_log_facility(): Samba4 now uses this Heimdal function, + which allows us to redirect the warnings and status from + the KDC (and client/server Kerberos code) to Samba's DEBUG() + system. Samba uses this logging routine optionally in the + main code, but it's required for KDC errors. + n. MSGSSMonger test-suite: Microsoft has released a krb-specific + testsuite called gssmonger, which tests interoperability. We + should compile it against lorikeet-heimdal& MIT and see if we + can build a 'Samba4' server for it. GSSMonger wasn't intended + to be Windows-specific. + o.Testsuite for kpasswd daemon: I have a partial kpasswd server + which needs finishing, and a Samba4 needs a client testsuite + written, either via the krb5 API or directly against GENSEC and + the ASN.1 routines. Samba4 likes to test failure-modes, not + just successful behavior. Currently Samba4's kpasswd only works + for Heimdal, not MIT clients. This may be due to call-ordering + constraints. + + +Appendix 1: Keytab Requirements + + Traditional 'MIT' keytab operation is very different from AD's + account-handling for application-servers: + a. Host PWs vs service-keys: + * Traditional 'MIT' behaviour is for the app-server to use a keytab + containing several named random-bitstring service-keys, created + by the KDC. An MIT-style keytab holds a different service-key + for every kerberized application-service that the server offers + to clients. Heimdal also implements this behaviour. MIT's model + doesn't use AD's UTF-16 'service password', and no salting is + necessary for service-keys, because each service-key is random + enough to withstand an exhaustive key-search attack. + * In the Windows model, the server key's construction is very + different: The app-server itself, not the KDC, generates a + random UTF-16 pseudo-textual password, and sends this password + to the KDC using SAMR, a DCE-RPC "domain-joining" protocol (but + for windows 7, see below). Then, the KDC shares this server- + password with every application service on the whole machine. + * Only when the app-server uses kerberos does the password get + salted by the member server (ie, an AD server-host). (That + is, no salt information appears to be conveyed from the AD KDC + to the member server, and the member server must use the rules + described in Luke's mail, in Appendix 3, below). The salted- + and-hashed version of the server-host's PW gets stored in the + server-host's keytab. + * Samba file-servers can have many server-names simultaneously + (kind of like web servers' software-virtual-hosting), but since + these servers are running in AD, these names can be set up to + all share the same secret key. In AD, co-located server names + almost always share a secret key like this. In samba3, this + key-sharing was optional, so some samba3 hosts' keytabs did + hold multiple keys. Samba4 abandons this traditional "old MIT" + style of keytab, and only supports one key per keytab, and + multiple server-names can use that keytab key in common. In + dealing with this model, Samba4 uses both the traditional file + keytab and an in-MEMORY keytabs. + * Pre-Windows7 AD and samba3/4 both use SAMR, an older protocol, + to jumpstart the member server's PW-sharing with AD (the "windows + domain-join process"). This PW-sharing transfers only the PW's + UTF-16 text, without any salting or hashing, so that non-krb + security mechanisms can use the same utf-16 text PW. For + Windows 7, this domain-joining uses LDAP for PW-setting. + b. Flexible server-naming + * The other big difference between AD's keytabs and MIT's is that + Windows offers a lot more flexibility about service-principals' + names. When the kerberos server-side library receives Windows-style tickets + from an app-client, MIT's krb library (or GSSAPI) must accommodate + Windows' flexibility about case-sensitivity and canonicalization. + This means that an incoming application-request to a member server + may use a wide variety of service-principal names. These include: + machine$@REALM (samba clients) + HOST/foo.bar@realm (win2k clients) + cifs/foo.bar@realm (winxp clients) + HOST/foo@realm (win2k clients, using netbios) + cifs/foo@realm (winxp clients, using netbios), + as well as all upper/lower-case variations on the above. + c. Keytabs& Name-canonicalization + * Heimdal's GSSAPI expects to to be called with a principal-name& a keytab, + possibly containing multiple principals' different keys. However, AD has + a different problem to solve, which is that the client may know the member- + server by a non-canonicalized principal name, yet AD knows the keytab + contains exactly one key, indexed by the canonical name. So, GSSAPI is + unprepared to canonicalize the server-name that the cliet requested, and + is also overprepared to do an unnecessary search through the keytab by + principal-name. So Samba's server-side GSSAPI calls have to "game" the + GSSAPI, by supplying the server's known canonical name, with the one-key + keytab. This doesn't really affect IPA's port of Samba4 to MIT-krb. + * Because the number of U/L case combinations got 'too hard' to put into + a keytab in the traditional way (with the client to specify the name), + we either pre-compute the keys into a traditional keytab or make an + in-MEMORY keytab at run time. In both cases we specify the principal + name to GSSAPI, which avoids the need to store duplicate principals. + * We use a 'private' keytab in our private dir, referenced from the + secrets.ldb by default. + +Appendix 2: KDC Plugin for Account-Authorization + +Here is how Samba4 ended up doing account-authorization in +Heimdal, via a specialized KDC plugin. This plugin helps +bridge an important gap: The user's AD record is much richer +than the Heimdal HDB format allows, so we do AD-specific +access-control checks in the plugin's AD-specific layer, +not in the DB-agnostic KDC server: + * We created a separate KDC plugin, with this API: + typedef struct + hdb_entry_ex { void *ctx; + hdb_entry entry; + void (*free_entry)(krb5_context, struct hdb_entry_ex *); + } hdb_entry_ex; + The void *ctx is a "private pointer," provided by the + 'get' method's hdb_entry_ex retval. The APIs below use + the void *ctx so as to find additional information about + the user, not contained in the hdb_entry structure. + Both the provider and the APIs below understand how to + cast the private void *ctx pointer. + typedef krb5_error_code + (*krb5plugin_windc_pac_generate)(void * krb5_context, + struct hdb_entry_ex *, + krb5_pac*); + typedef krb5_error_code + (*krb5plugin_windc_pac_verify)(void * krb5_context, + const krb5_principal, + struct hdb_entry_ex *, + struct hdb_entry_ex *, + krb5_pac *); + typedef krb5_error_code + (*krb5plugin_windc_client_access)(void * krb5_context, + struct hdb_entry_ex *, + KDC_REQ *, + krb5_data *); + The krb5_data* here is critical, so that samba's KDC can return + the right NTSTATUS code in the 'error string' returned to the + client. Otherwise, the windows client won't get the right error + message to the user (such as 'password expired' etc). The pure + Kerberos error is not enough) + typedef struct + krb5plugin_windc_ftable { int minor_version; + krb5_error_code (*init)(krb5_context, void **); + void (*fini)(void *); + krb5plugin_windc_pac_generate pac_generate; + krb5plugin_windc_pac_verify pac_verify; + krb5plugin_windc_client_access client_access; + } krb5plugin_windc_ftable; + This API has some Heimdal-specific stuff, that'll + have to change when we port this KDC plugin to MIT krb. + * 1st callback (pac_generate) creates an initial PAC from the user's AD record. + * 2nd callback (pac_verify) checks that a PAC is correctly signed, + adds additional groups (for cross-realm tickets) + and re-signs with the key of the target kerberos + service's account + * 3rd callback (client_access) performs additional access checks, such as + allowedWorkstations and account expiry. + * For example, to register this plugin, use the kdc's standard + plugin-system at Samba4's initialisation: + /* first, setup the table of callback pointers */ + /* Registar WinDC hooks */ + ret = krb5_plugin_register(krb5_context, PLUGIN_TYPE_DATA, + "windc",&windc_plugin_table); + /* once registered, the KDC will invoke the callbacks */ + /* while preparing each new ticket (TGT or app-tkt) */ + * An alternative way to register the plugin is with a + config-file that names a DSO (Dynamically Shared Object). + +Appendix 3: Samba4 stuff that doesn't need to get ported. + +Heimdal oddities +* Heimdal is built such that it should be able to serve multiple realms + at the same time. This isn't relevant for Samba's use, but it shows + up in a lot of generalisations throughout the code. +* Samba4's code originally tried internally to make it possible to use + Heimdal's multi-realms-per-KDC ability, but this was ill-conceived, + and AB has recently (6/09) ripped the last of that multi-realms + stuff out of samba4. AB says that in AD, it's not really possible + to make this work; several AD components structurally assume that + there's one realm per KDC. However, we do use this to support + canonicalization of realm-names: case variations, plus long-vs-short + variants of realm-names. No MIT porting task here, as long as MIT kdc + doesn't refuse to do some LDAP lookups (eg, alias' realm-name looks + wrong). +* Heimdal supports multiple passwords on a client account: Samba4 + seems to call hdb_next_enctype2key() in the pre-authentication + routines, to allow multiple passwords per account in krb5. + (I think this was intended to allow multiple salts). AD doesn't + support this, so the MIT port shouldn't bother with this. +Not needed anymore, because MIT's code now handles PACs fully: +* gss_krb5_copy_service_keyblock() (get the key used to actually + encrypt the ticket to the server, because the same key is used for + the PAC validation). +* gsskrb5_extract_authtime_from_sec_context (get authtime from + kerberos ticket) +* gsskrb5_extract_authz_data_from_sec_context (get authdata from + ticket, ie the PAC. Must unwrap the data if in an AD-IFRELEVANT)] +Authz data extraction +* We use krb5_ticket_get_authorization_data_type(), and expect + it to return the correct authz data, even if wrapped in an + AD-IFRELEVANT container. This doesn't need to be ported to MIT. + This should be obsoleted by MIT's new PAC code. +libkdc +* Samba4 needs to be built as a single binary (design requirement), + and this should include the KDC. Samba also (and perhaps more + importantly) needs to control the configuration environment of + the KDC. +* But, libkdc doesn't matter for IPA; Samba invokes the Heimdal kdc + as a library call, but this is just a convenience, and the MIT + port can do otherwise w/o trouble.) +Returned Salt for PreAuthentication + When the AD-KDC replies to pre-authentication, it returns the + salt, which may be in the form of a principalName that is in no + way connected with the current names. (ie, even if the + userPrincipalName and samAccountName are renamed, the old salt + is returned). + This is the kerberos standard salt, kept in the 'Key'. The + AD generation rules are found in a Mail from Luke Howard dated + 10 Nov 2004. The MIT glue layer doesn't really need to care about + these salt-handling details; the samba4 code& the LDAP backend + will conspire to make sure that MIT's KDC gets correct salts. + > + > From: Luke Howard<lukeh@padl.com> + > Organization: PADL Software Pty Ltd + > To: lukeh@padl.com + > Date: Wed, 10 Nov 2004 13:31:21 +1100 + > Cc: huaraz@moeller.plus.com, samba-technical@lists.samba.org + > Subject: Re: Samba-3.0.7-1.3E Active Directory Issues + > ------- + > + > Did some more testing, it appears the behaviour has another + > explanation. It appears that the standard Kerberos password salt + > algorithm is applied in Windows 2003, just that the source principal + > name is different. + > + > Here is what I've been able to deduce from creating a bunch of + > different accounts: + > [SAM name in this mail means the AD attribute samAccountName . + > E.g., jbob for a user and jbcomputer$ for a computer.] + > + > [UPN is the AD userPrincipalName attribute. For example, jbob@mydomain.com] + > Type of account Principal for Salting + > ======================================================================== + > Computer Account host/<SAM-Name-Without-$>.realm@REALM + > User Account Without UPN<SAM-Name>@REALM + > User Account With UPN<LHS-Of-UPN>@REALM + > + > Note that if the computer account's SAM account name does not include + > the trailing '$', then the entire SAM account name is used as input to + > the salting principal. Setting a UPN for a computer account has no + > effect. + > + > It seems to me odd that the RHS of the UPN is not used in the salting + > principal. For example, a user with UPN foo@mydomain.com in the realm + > MYREALM.COM would have a salt of MYREALM.COMfoo. Perhaps this is to + > allow a user's UPN suffix to be changed without changing the salt. And + > perhaps using the UPN for salting signifies a move away SAM names and + > their associated constraints. + > + > For more information on how UPNs relate to the Kerberos protocol, + > see: + > + > http://www.ietf.org/proceedings/01dec/I-D/draft-ietf-krb-wg-kerberos-referrals-02.txt + > + > -- Luke diff --git a/source4/auth/kerberos/kerberos.h b/source4/auth/kerberos/kerberos.h new file mode 100644 index 0000000..33ee4f3 --- /dev/null +++ b/source4/auth/kerberos/kerberos.h @@ -0,0 +1,87 @@ +/* + Unix SMB/CIFS implementation. + simple kerberos5 routines for active directory + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Luke Howard 2002-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _AUTH_KERBEROS_H_ +#define _AUTH_KERBEROS_H_ + +#if defined(HAVE_KRB5) + +#include "system/kerberos.h" +#include "auth/kerberos/krb5_init_context.h" +#include "librpc/gen_ndr/krb5pac.h" +#include "lib/krb5_wrap/krb5_samba.h" + +struct auth_user_info_dc; +struct cli_credentials; + +struct ccache_container { + struct smb_krb5_context *smb_krb5_context; + krb5_ccache ccache; +}; + +struct keytab_container { + struct smb_krb5_context *smb_krb5_context; + krb5_keytab keytab; + bool password_based; +}; + +/* not really ASN.1, but RFC 1964 */ +#define TOK_ID_KRB_AP_REQ ((const uint8_t *)"\x01\x00") +#define TOK_ID_KRB_AP_REP ((const uint8_t *)"\x02\x00") +#define TOK_ID_KRB_ERROR ((const uint8_t *)"\x03\x00") +#define TOK_ID_GSS_GETMIC ((const uint8_t *)"\x01\x01") +#define TOK_ID_GSS_WRAP ((const uint8_t *)"\x02\x01") + +#define ENC_ALL_TYPES (ENC_RC4_HMAC_MD5 | \ + ENC_HMAC_SHA1_96_AES128 | ENC_HMAC_SHA1_96_AES256) + +#ifndef HAVE_KRB5_SET_DEFAULT_TGS_KTYPES +krb5_error_code krb5_set_default_tgs_ktypes(krb5_context ctx, const krb5_enctype *enc); +#endif + +#if defined(HAVE_KRB5_AUTH_CON_SETKEY) && !defined(HAVE_KRB5_AUTH_CON_SETUSERUSERKEY) +krb5_error_code krb5_auth_con_setuseruserkey(krb5_context context, krb5_auth_context auth_context, krb5_keyblock *keyblock); +#endif + +#if defined(HAVE_KRB5_PRINCIPAL_GET_COMP_STRING) && !defined(HAVE_KRB5_PRINC_COMPONENT) +const krb5_data *krb5_princ_component(krb5_context context, krb5_principal principal, int i ); +#endif + +/* Samba wrapper function for krb5 functionality. */ + krb5_error_code kerberos_encode_pac(TALLOC_CTX *mem_ctx, + struct PAC_DATA *pac_data, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + DATA_BLOB *pac); + krb5_error_code kerberos_create_pac(TALLOC_CTX *mem_ctx, + struct auth_user_info_dc *user_info_dc, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + krb5_principal client_principal, + time_t tgs_authtime, + DATA_BLOB *pac); + +#include "auth/kerberos/proto.h" + +#endif /* HAVE_KRB5 */ + +#endif /* _AUTH_KERBEROS_H_ */ diff --git a/source4/auth/kerberos/kerberos_credentials.h b/source4/auth/kerberos/kerberos_credentials.h new file mode 100644 index 0000000..362edf7 --- /dev/null +++ b/source4/auth/kerberos/kerberos_credentials.h @@ -0,0 +1,37 @@ +/* + Unix SMB/CIFS implementation. + + Kerberos utility functions for GENSEC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +krb5_error_code kinit_to_ccache(TALLOC_CTX *parent_ctx, + struct cli_credentials *credentials, + struct smb_krb5_context *smb_krb5_context, + struct tevent_context *event_ctx, + krb5_ccache ccache, + enum credentials_obtained *obtained, + const char **error_string); + +/* Manually prototyped here to avoid needing krb5 headers in most callers */ +krb5_error_code principal_from_credentials(TALLOC_CTX *parent_ctx, + struct cli_credentials *credentials, + struct smb_krb5_context *smb_krb5_context, + krb5_principal *princ, + enum credentials_obtained *obtained, + const char **error_string); diff --git a/source4/auth/kerberos/kerberos_pac.c b/source4/auth/kerberos/kerberos_pac.c new file mode 100644 index 0000000..156a140 --- /dev/null +++ b/source4/auth/kerberos/kerberos_pac.c @@ -0,0 +1,496 @@ +/* + Unix SMB/CIFS implementation. + + Create and parse the krb5 PAC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005,2008 + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Luke Howard 2002-2003 + Copyright (C) Stefan Metzmacher 2004-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/kerberos.h" +#include "auth/auth.h" +#include "auth/kerberos/kerberos.h" +#include "librpc/gen_ndr/ndr_krb5pac.h" +#include <ldb.h> +#include "auth/auth_sam_reply.h" +#include "auth/credentials/credentials.h" +#include "auth/kerberos/kerberos_util.h" +#include "auth/kerberos/pac_utils.h" + + krb5_error_code kerberos_encode_pac(TALLOC_CTX *mem_ctx, + struct PAC_DATA *pac_data, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + DATA_BLOB *pac) +{ + NTSTATUS nt_status; + krb5_error_code ret; + enum ndr_err_code ndr_err; + DATA_BLOB zero_blob = data_blob(NULL, 0); + DATA_BLOB tmp_blob = data_blob(NULL, 0); + struct PAC_SIGNATURE_DATA *kdc_checksum = NULL; + struct PAC_SIGNATURE_DATA *srv_checksum = NULL; + uint32_t i; + + /* First, just get the keytypes filled in (and lengths right, eventually) */ + for (i=0; i < pac_data->num_buffers; i++) { + if (pac_data->buffers[i].type != PAC_TYPE_KDC_CHECKSUM) { + continue; + } + kdc_checksum = &pac_data->buffers[i].info->kdc_cksum, + ret = smb_krb5_make_pac_checksum(mem_ctx, + &zero_blob, + context, + krbtgt_keyblock, + &kdc_checksum->type, + &kdc_checksum->signature); + if (ret) { + DEBUG(2, ("making krbtgt PAC checksum failed: %s\n", + smb_get_krb5_error_message(context, ret, mem_ctx))); + talloc_free(pac_data); + return ret; + } + } + + for (i=0; i < pac_data->num_buffers; i++) { + if (pac_data->buffers[i].type != PAC_TYPE_SRV_CHECKSUM) { + continue; + } + srv_checksum = &pac_data->buffers[i].info->srv_cksum; + ret = smb_krb5_make_pac_checksum(mem_ctx, + &zero_blob, + context, + service_keyblock, + &srv_checksum->type, + &srv_checksum->signature); + if (ret) { + DEBUG(2, ("making service PAC checksum failed: %s\n", + smb_get_krb5_error_message(context, ret, mem_ctx))); + talloc_free(pac_data); + return ret; + } + } + + if (!kdc_checksum) { + DEBUG(2, ("Invalid PAC constructed for signing, no KDC checksum present!")); + return EINVAL; + } + if (!srv_checksum) { + DEBUG(2, ("Invalid PAC constructed for signing, no SRV checksum present!")); + return EINVAL; + } + + /* But wipe out the actual signatures */ + memset(kdc_checksum->signature.data, '\0', kdc_checksum->signature.length); + memset(srv_checksum->signature.data, '\0', srv_checksum->signature.length); + + ndr_err = ndr_push_struct_blob(&tmp_blob, mem_ctx, + pac_data, + (ndr_push_flags_fn_t)ndr_push_PAC_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(1, ("PAC (presig) push failed: %s\n", nt_errstr(nt_status))); + talloc_free(pac_data); + return EINVAL; + } + + /* Then sign the result of the previous push, where the sig was zero'ed out */ + ret = smb_krb5_make_pac_checksum(mem_ctx, + &tmp_blob, + context, + service_keyblock, + &srv_checksum->type, + &srv_checksum->signature); + + if (ret) { + DBG_WARNING("making krbtgt PAC srv_checksum failed: %s\n", + smb_get_krb5_error_message(context, ret, mem_ctx)); + talloc_free(pac_data); + return ret; + } + + /* Then sign Server checksum */ + ret = smb_krb5_make_pac_checksum(mem_ctx, + &srv_checksum->signature, + context, + krbtgt_keyblock, + &kdc_checksum->type, + &kdc_checksum->signature); + if (ret) { + DBG_WARNING("making krbtgt PAC kdc_checksum failed: %s\n", + smb_get_krb5_error_message(context, ret, mem_ctx)); + talloc_free(pac_data); + return ret; + } + + /* And push it out again, this time to the world. This relies on determanistic pointer values */ + ndr_err = ndr_push_struct_blob(&tmp_blob, mem_ctx, + pac_data, + (ndr_push_flags_fn_t)ndr_push_PAC_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(1, ("PAC (final) push failed: %s\n", nt_errstr(nt_status))); + talloc_free(pac_data); + return EINVAL; + } + + *pac = tmp_blob; + + return ret; +} + + + krb5_error_code kerberos_create_pac(TALLOC_CTX *mem_ctx, + struct auth_user_info_dc *user_info_dc, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + krb5_principal client_principal, + time_t tgs_authtime, + DATA_BLOB *pac) +{ + NTSTATUS nt_status; + krb5_error_code ret; + struct PAC_DATA *pac_data = talloc(mem_ctx, struct PAC_DATA); + struct netr_SamInfo3 *sam3; + union PAC_INFO *u_LOGON_INFO; + struct PAC_LOGON_INFO *LOGON_INFO; + union PAC_INFO *u_LOGON_NAME; + struct PAC_LOGON_NAME *LOGON_NAME; + union PAC_INFO *u_KDC_CHECKSUM; + union PAC_INFO *u_SRV_CHECKSUM; + + char *name; + + enum { + PAC_BUF_LOGON_INFO = 0, + PAC_BUF_LOGON_NAME = 1, + PAC_BUF_SRV_CHECKSUM = 2, + PAC_BUF_KDC_CHECKSUM = 3, + PAC_BUF_NUM_BUFFERS = 4 + }; + + if (!pac_data) { + return ENOMEM; + } + + pac_data->num_buffers = PAC_BUF_NUM_BUFFERS; + pac_data->version = 0; + + pac_data->buffers = talloc_array(pac_data, + struct PAC_BUFFER, + pac_data->num_buffers); + if (!pac_data->buffers) { + talloc_free(pac_data); + return ENOMEM; + } + + /* LOGON_INFO */ + u_LOGON_INFO = talloc_zero(pac_data->buffers, union PAC_INFO); + if (!u_LOGON_INFO) { + talloc_free(pac_data); + return ENOMEM; + } + pac_data->buffers[PAC_BUF_LOGON_INFO].type = PAC_TYPE_LOGON_INFO; + pac_data->buffers[PAC_BUF_LOGON_INFO].info = u_LOGON_INFO; + + /* LOGON_NAME */ + u_LOGON_NAME = talloc_zero(pac_data->buffers, union PAC_INFO); + if (!u_LOGON_NAME) { + talloc_free(pac_data); + return ENOMEM; + } + pac_data->buffers[PAC_BUF_LOGON_NAME].type = PAC_TYPE_LOGON_NAME; + pac_data->buffers[PAC_BUF_LOGON_NAME].info = u_LOGON_NAME; + LOGON_NAME = &u_LOGON_NAME->logon_name; + + /* SRV_CHECKSUM */ + u_SRV_CHECKSUM = talloc_zero(pac_data->buffers, union PAC_INFO); + if (!u_SRV_CHECKSUM) { + talloc_free(pac_data); + return ENOMEM; + } + pac_data->buffers[PAC_BUF_SRV_CHECKSUM].type = PAC_TYPE_SRV_CHECKSUM; + pac_data->buffers[PAC_BUF_SRV_CHECKSUM].info = u_SRV_CHECKSUM; + + /* KDC_CHECKSUM */ + u_KDC_CHECKSUM = talloc_zero(pac_data->buffers, union PAC_INFO); + if (!u_KDC_CHECKSUM) { + talloc_free(pac_data); + return ENOMEM; + } + pac_data->buffers[PAC_BUF_KDC_CHECKSUM].type = PAC_TYPE_KDC_CHECKSUM; + pac_data->buffers[PAC_BUF_KDC_CHECKSUM].info = u_KDC_CHECKSUM; + + /* now the real work begins... */ + + LOGON_INFO = talloc_zero(u_LOGON_INFO, struct PAC_LOGON_INFO); + if (!LOGON_INFO) { + talloc_free(pac_data); + return ENOMEM; + } + nt_status = auth_convert_user_info_dc_saminfo3(LOGON_INFO, user_info_dc, &sam3); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(1, ("Getting Samba info failed: %s\n", nt_errstr(nt_status))); + talloc_free(pac_data); + return EINVAL; + } + + u_LOGON_INFO->logon_info.info = LOGON_INFO; + LOGON_INFO->info3 = *sam3; + + ret = krb5_unparse_name_flags(context, client_principal, + KRB5_PRINCIPAL_UNPARSE_NO_REALM | + KRB5_PRINCIPAL_UNPARSE_DISPLAY, + &name); + if (ret) { + return ret; + } + LOGON_NAME->account_name = talloc_strdup(LOGON_NAME, name); + free(name); + /* + this logon_time field is absolutely critical. This is what + caused all our PAC troubles :-) + */ + unix_to_nt_time(&LOGON_NAME->logon_time, tgs_authtime); + + ret = kerberos_encode_pac(mem_ctx, + pac_data, + context, + krbtgt_keyblock, + service_keyblock, + pac); + talloc_free(pac_data); + return ret; +} + +static krb5_error_code kerberos_pac_buffer_present(krb5_context context, + const krb5_pac pac, + uint32_t type) +{ +#ifdef SAMBA4_USES_HEIMDAL + return krb5_pac_get_buffer(context, pac, type, NULL); +#else /* MIT */ + krb5_error_code ret; + krb5_data data; + + /* + * MIT won't let us pass NULL for the data parameter, so we are forced + * to allocate a new buffer and then immediately free it. + */ + ret = krb5_pac_get_buffer(context, pac, type, &data); + if (ret == 0) { + krb5_free_data_contents(context, &data); + } + return ret; +#endif /* SAMBA4_USES_HEIMDAL */ +} + +krb5_error_code kerberos_pac_to_user_info_dc(TALLOC_CTX *mem_ctx, + krb5_pac pac, + krb5_context context, + struct auth_user_info_dc **user_info_dc, + struct PAC_SIGNATURE_DATA *pac_srv_sig, + struct PAC_SIGNATURE_DATA *pac_kdc_sig) +{ + NTSTATUS nt_status; + enum ndr_err_code ndr_err; + krb5_error_code ret; + + DATA_BLOB pac_logon_info_in, pac_srv_checksum_in, pac_kdc_checksum_in; + krb5_data k5pac_logon_info_in, k5pac_srv_checksum_in, k5pac_kdc_checksum_in; + DATA_BLOB pac_upn_dns_info_in; + krb5_data k5pac_upn_dns_info_in; + + union PAC_INFO info; + union PAC_INFO _upn_dns_info; + struct PAC_UPN_DNS_INFO *upn_dns_info = NULL; + struct auth_user_info_dc *user_info_dc_out; + + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + + if (!tmp_ctx) { + return ENOMEM; + } + + ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_LOGON_INFO, &k5pac_logon_info_in); + if (ret != 0) { + talloc_free(tmp_ctx); + return EINVAL; + } + + pac_logon_info_in = data_blob_const(k5pac_logon_info_in.data, k5pac_logon_info_in.length); + + ndr_err = ndr_pull_union_blob(&pac_logon_info_in, tmp_ctx, &info, + PAC_TYPE_LOGON_INFO, + (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO); + smb_krb5_free_data_contents(context, &k5pac_logon_info_in); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the PAC LOGON_INFO: %s\n", nt_errstr(nt_status))); + talloc_free(tmp_ctx); + return EINVAL; + } + if (info.logon_info.info == NULL) { + DEBUG(0,("can't parse the PAC LOGON_INFO: missing info pointer\n")); + talloc_free(tmp_ctx); + return EINVAL; + } + + ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_UPN_DNS_INFO, + &k5pac_upn_dns_info_in); + if (ret == ENOENT) { + ZERO_STRUCT(k5pac_upn_dns_info_in); + ret = 0; + } + if (ret != 0) { + talloc_free(tmp_ctx); + return EINVAL; + } + + pac_upn_dns_info_in = data_blob_const(k5pac_upn_dns_info_in.data, + k5pac_upn_dns_info_in.length); + + if (pac_upn_dns_info_in.length != 0) { + ndr_err = ndr_pull_union_blob(&pac_upn_dns_info_in, tmp_ctx, + &_upn_dns_info, + PAC_TYPE_UPN_DNS_INFO, + (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO); + smb_krb5_free_data_contents(context, &k5pac_upn_dns_info_in); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the PAC UPN_DNS_INFO: %s\n", + nt_errstr(nt_status))); + talloc_free(tmp_ctx); + return EINVAL; + } + upn_dns_info = &_upn_dns_info.upn_dns_info; + } + + /* Pull this right into the normal auth sysstem structures */ + nt_status = make_user_info_dc_pac(mem_ctx, + info.logon_info.info, + upn_dns_info, + &user_info_dc_out); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_ERR("make_user_info_dc_pac() failed -%s\n", + nt_errstr(nt_status)); + NDR_PRINT_DEBUG(PAC_LOGON_INFO, info.logon_info.info); + if (upn_dns_info != NULL) { + NDR_PRINT_DEBUG(PAC_UPN_DNS_INFO, upn_dns_info); + } + talloc_free(tmp_ctx); + return EINVAL; + } + + if (pac_srv_sig) { + ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_SRV_CHECKSUM, &k5pac_srv_checksum_in); + if (ret != 0) { + talloc_free(tmp_ctx); + return ret; + } + + pac_srv_checksum_in = data_blob_const(k5pac_srv_checksum_in.data, k5pac_srv_checksum_in.length); + + ndr_err = ndr_pull_struct_blob(&pac_srv_checksum_in, pac_srv_sig, + pac_srv_sig, + (ndr_pull_flags_fn_t)ndr_pull_PAC_SIGNATURE_DATA); + smb_krb5_free_data_contents(context, &k5pac_srv_checksum_in); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the KDC signature: %s\n", + nt_errstr(nt_status))); + return EINVAL; + } + } + + if (pac_kdc_sig) { + ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_KDC_CHECKSUM, &k5pac_kdc_checksum_in); + if (ret != 0) { + talloc_free(tmp_ctx); + return ret; + } + + pac_kdc_checksum_in = data_blob_const(k5pac_kdc_checksum_in.data, k5pac_kdc_checksum_in.length); + + ndr_err = ndr_pull_struct_blob(&pac_kdc_checksum_in, pac_kdc_sig, + pac_kdc_sig, + (ndr_pull_flags_fn_t)ndr_pull_PAC_SIGNATURE_DATA); + smb_krb5_free_data_contents(context, &k5pac_kdc_checksum_in); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the KDC signature: %s\n", + nt_errstr(nt_status))); + return EINVAL; + } + } + + /* + * Based on the presence of a REQUESTER_SID PAC buffer, ascertain + * whether the ticket is a TGT. This helps the KDC and kpasswd service + * ensure they do not accept tickets meant for the other. + * + * This heuristic will fail for older Samba versions and Windows prior + * to Nov. 2021 updates, which lack support for the REQUESTER_SID PAC + * buffer. + */ + ret = kerberos_pac_buffer_present(context, pac, PAC_TYPE_REQUESTER_SID); + if (ret == ENOENT) { + /* This probably isn't a TGT. */ + user_info_dc_out->ticket_type = TICKET_TYPE_NON_TGT; + } else if (ret != 0) { + talloc_free(tmp_ctx); + return ret; + } else { + /* This probably is a TGT. */ + user_info_dc_out->ticket_type = TICKET_TYPE_TGT; + } + + *user_info_dc = user_info_dc_out; + + return 0; +} + + +NTSTATUS kerberos_pac_blob_to_user_info_dc(TALLOC_CTX *mem_ctx, + DATA_BLOB pac_blob, + krb5_context context, + struct auth_user_info_dc **user_info_dc, + struct PAC_SIGNATURE_DATA *pac_srv_sig, + struct PAC_SIGNATURE_DATA *pac_kdc_sig) +{ + krb5_error_code ret; + krb5_pac pac; + ret = krb5_pac_parse(context, + pac_blob.data, pac_blob.length, + &pac); + if (ret) { + return map_nt_error_from_unix_common(ret); + } + + + ret = kerberos_pac_to_user_info_dc(mem_ctx, pac, context, user_info_dc, pac_srv_sig, pac_kdc_sig); + krb5_pac_free(context, pac); + if (ret) { + return map_nt_error_from_unix_common(ret); + } + return NT_STATUS_OK; +} diff --git a/source4/auth/kerberos/kerberos_util.c b/source4/auth/kerberos/kerberos_util.c new file mode 100644 index 0000000..c14d8c7 --- /dev/null +++ b/source4/auth/kerberos/kerberos_util.c @@ -0,0 +1,648 @@ +/* + Unix SMB/CIFS implementation. + + Kerberos utility functions for GENSEC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" +#include "auth/credentials/credentials.h" +#include "auth/credentials/credentials_krb5.h" +#include "auth/kerberos/kerberos_credentials.h" +#include "auth/kerberos/kerberos_util.h" + +struct principal_container { + struct smb_krb5_context *smb_krb5_context; + krb5_principal principal; + const char *string_form; /* Optional */ +}; + +static krb5_error_code free_principal(struct principal_container *pc) +{ + /* current heimdal - 0.6.3, which we need anyway, fixes segfaults here */ + krb5_free_principal(pc->smb_krb5_context->krb5_context, pc->principal); + + return 0; +} + + +static krb5_error_code parse_principal(TALLOC_CTX *parent_ctx, + const char *princ_string, + struct smb_krb5_context *smb_krb5_context, + krb5_principal *princ, + const char **error_string) +{ + int ret; + struct principal_container *mem_ctx; + if (princ_string == NULL) { + *princ = NULL; + return 0; + } + + ret = krb5_parse_name(smb_krb5_context->krb5_context, + princ_string, princ); + + if (ret) { + (*error_string) = smb_get_krb5_error_message( + smb_krb5_context->krb5_context, + ret, parent_ctx); + return ret; + } + + mem_ctx = talloc(parent_ctx, struct principal_container); + if (!mem_ctx) { + (*error_string) = error_message(ENOMEM); + return ENOMEM; + } + + /* This song-and-dance effectivly puts the principal + * into talloc, so we can't loose it. */ + mem_ctx->smb_krb5_context = talloc_reference(mem_ctx, + smb_krb5_context); + mem_ctx->principal = *princ; + talloc_set_destructor(mem_ctx, free_principal); + return 0; +} + +/* Obtain the principal set on this context. Requires a + * smb_krb5_context because we are doing krb5 principal parsing with + * the library routines. The returned princ is placed in the talloc + * system by means of a destructor (do *not* free). */ + +krb5_error_code principal_from_credentials(TALLOC_CTX *parent_ctx, + struct cli_credentials *credentials, + struct smb_krb5_context *smb_krb5_context, + krb5_principal *princ, + enum credentials_obtained *obtained, + const char **error_string) +{ + krb5_error_code ret; + const char *princ_string; + TALLOC_CTX *mem_ctx = talloc_new(parent_ctx); + *obtained = CRED_UNINITIALISED; + + if (!mem_ctx) { + (*error_string) = error_message(ENOMEM); + return ENOMEM; + } + princ_string = cli_credentials_get_principal_and_obtained(credentials, + mem_ctx, + obtained); + if (!princ_string) { + *princ = NULL; + return 0; + } + + ret = parse_principal(parent_ctx, princ_string, + smb_krb5_context, princ, error_string); + talloc_free(mem_ctx); + return ret; +} + +/* Obtain the principal set on this context. Requires a + * smb_krb5_context because we are doing krb5 principal parsing with + * the library routines. The returned princ is placed in the talloc + * system by means of a destructor (do *not* free). */ + +static krb5_error_code impersonate_principal_from_credentials( + TALLOC_CTX *parent_ctx, + struct cli_credentials *credentials, + struct smb_krb5_context *smb_krb5_context, + krb5_principal *princ, + const char **error_string) +{ + return parse_principal(parent_ctx, + cli_credentials_get_impersonate_principal(credentials), + smb_krb5_context, princ, error_string); +} + +krb5_error_code smb_krb5_create_principals_array(TALLOC_CTX *mem_ctx, + krb5_context context, + const char *account_name, + const char *realm, + uint32_t num_spns, + const char *spns[], + uint32_t *pnum_principals, + krb5_principal **pprincipals, + const char **error_string) +{ + krb5_error_code code; + TALLOC_CTX *tmp_ctx; + uint32_t num_principals = 0; + krb5_principal *principals; + uint32_t i; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + *error_string = "Cannot allocate tmp_ctx"; + return ENOMEM; + } + + if (realm == NULL) { + *error_string = "Cannot create principal without a realm"; + code = EINVAL; + goto done; + } + + if (account_name == NULL && (num_spns == 0 || spns == NULL)) { + *error_string = "Cannot create principal without an account or SPN"; + code = EINVAL; + goto done; + } + + if (account_name != NULL && account_name[0] != '\0') { + num_principals++; + } + num_principals += num_spns; + + principals = talloc_zero_array(tmp_ctx, + krb5_principal, + num_principals); + if (principals == NULL) { + *error_string = "Cannot allocate principals"; + code = ENOMEM; + goto done; + } + + for (i = 0; i < num_spns; i++) { + code = krb5_parse_name(context, spns[i], &(principals[i])); + if (code != 0) { + *error_string = smb_get_krb5_error_message(context, + code, + mem_ctx); + goto done; + } + } + + if (account_name != NULL && account_name[0] != '\0') { + code = smb_krb5_make_principal(context, + &(principals[i]), + realm, + account_name, + NULL); + if (code != 0) { + *error_string = smb_get_krb5_error_message(context, + code, + mem_ctx); + goto done; + } + } + + if (pnum_principals != NULL) { + *pnum_principals = num_principals; + + if (pprincipals != NULL) { + *pprincipals = talloc_steal(mem_ctx, principals); + } + } + + code = 0; +done: + talloc_free(tmp_ctx); + return code; +} + +/** + * Return a freshly allocated ccache (destroyed by destructor on child + * of parent_ctx), for a given set of client credentials + */ + + krb5_error_code kinit_to_ccache(TALLOC_CTX *parent_ctx, + struct cli_credentials *credentials, + struct smb_krb5_context *smb_krb5_context, + struct tevent_context *event_ctx, + krb5_ccache ccache, + enum credentials_obtained *obtained, + const char **error_string) +{ + krb5_error_code ret; + const char *password; + const char *self_service; + const char *target_service; + time_t kdc_time = 0; + krb5_principal princ; + krb5_principal impersonate_principal; + int tries; + TALLOC_CTX *mem_ctx = talloc_new(parent_ctx); + krb5_get_init_creds_opt *krb_options; + + if (!mem_ctx) { + (*error_string) = strerror(ENOMEM); + return ENOMEM; + } + + ret = principal_from_credentials(mem_ctx, credentials, smb_krb5_context, &princ, obtained, error_string); + if (ret) { + talloc_free(mem_ctx); + return ret; + } + + if (princ == NULL) { + (*error_string) = talloc_asprintf(credentials, "principal, username or realm was not specified in the credentials"); + talloc_free(mem_ctx); + return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; + } + + ret = impersonate_principal_from_credentials(mem_ctx, credentials, smb_krb5_context, &impersonate_principal, error_string); + if (ret) { + talloc_free(mem_ctx); + return ret; + } + + self_service = cli_credentials_get_self_service(credentials); + target_service = cli_credentials_get_target_service(credentials); + + password = cli_credentials_get_password(credentials); + + /* setup the krb5 options we want */ + if ((ret = krb5_get_init_creds_opt_alloc(smb_krb5_context->krb5_context, &krb_options))) { + (*error_string) = talloc_asprintf(credentials, "krb5_get_init_creds_opt_alloc failed (%s)\n", + smb_get_krb5_error_message(smb_krb5_context->krb5_context, + ret, mem_ctx)); + talloc_free(mem_ctx); + return ret; + } + +#ifdef SAMBA4_USES_HEIMDAL /* Disable for now MIT reads defaults when needed */ + /* get the defaults */ + krb5_get_init_creds_opt_set_default_flags(smb_krb5_context->krb5_context, NULL, NULL, krb_options); +#endif + /* set if we want a forwardable ticket */ + switch (cli_credentials_get_krb_forwardable(credentials)) { + case CRED_AUTO_KRB_FORWARDABLE: + break; + case CRED_NO_KRB_FORWARDABLE: + krb5_get_init_creds_opt_set_forwardable(krb_options, FALSE); + break; + case CRED_FORCE_KRB_FORWARDABLE: + krb5_get_init_creds_opt_set_forwardable(krb_options, TRUE); + break; + } + +#ifdef SAMBA4_USES_HEIMDAL /* FIXME: MIT does not have this yet */ + /* + * In order to work against windows KDCs even if we use + * the netbios domain name as realm, we need to add the following + * flags: + * KRB5_INIT_CREDS_NO_C_CANON_CHECK; + * KRB5_INIT_CREDS_NO_C_NO_EKU_CHECK; + * + * On MIT: Set pkinit_eku_checking to none + */ + krb5_get_init_creds_opt_set_win2k(smb_krb5_context->krb5_context, + krb_options, true); + krb5_get_init_creds_opt_set_canonicalize(smb_krb5_context->krb5_context, + krb_options, true); +#else /* MIT */ + krb5_get_init_creds_opt_set_canonicalize(krb_options, true); +#endif + + tries = 2; + while (tries--) { +#ifdef SAMBA4_USES_HEIMDAL + struct tevent_context *previous_ev; + /* Do this every time, in case we have weird recursive issues here */ + ret = smb_krb5_context_set_event_ctx(smb_krb5_context, event_ctx, &previous_ev); + if (ret) { + talloc_free(mem_ctx); + return ret; + } +#endif + if (password) { + if (impersonate_principal) { + ret = smb_krb5_kinit_s4u2_ccache(smb_krb5_context->krb5_context, + ccache, + princ, + password, + impersonate_principal, + self_service, + target_service, + krb_options, + NULL, + &kdc_time); + } else { + ret = smb_krb5_kinit_password_ccache(smb_krb5_context->krb5_context, + ccache, + princ, + password, + target_service, + krb_options, + NULL, + &kdc_time); + } + } else if (impersonate_principal) { + talloc_free(mem_ctx); + (*error_string) = "INTERNAL error: Cannot impersonate principal with just a keyblock. A password must be specified in the credentials"; + return EINVAL; + } else { + /* No password available, try to use a keyblock instead */ + + krb5_keyblock keyblock; + const struct samr_Password *mach_pwd; + mach_pwd = cli_credentials_get_nt_hash(credentials, mem_ctx); + if (!mach_pwd) { + talloc_free(mem_ctx); + (*error_string) = "kinit_to_ccache: No password available for kinit\n"; + krb5_get_init_creds_opt_free(smb_krb5_context->krb5_context, krb_options); +#ifdef SAMBA4_USES_HEIMDAL + smb_krb5_context_remove_event_ctx(smb_krb5_context, previous_ev, event_ctx); +#endif + return EINVAL; + } + ret = smb_krb5_keyblock_init_contents(smb_krb5_context->krb5_context, + ENCTYPE_ARCFOUR_HMAC, + mach_pwd->hash, sizeof(mach_pwd->hash), + &keyblock); + + if (ret == 0) { + ret = smb_krb5_kinit_keyblock_ccache(smb_krb5_context->krb5_context, + ccache, + princ, + &keyblock, + target_service, + krb_options, + NULL, + &kdc_time); + krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &keyblock); + } + } + +#ifdef SAMBA4_USES_HEIMDAL + smb_krb5_context_remove_event_ctx(smb_krb5_context, previous_ev, event_ctx); +#endif + + if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) { + /* Perhaps we have been given an invalid skew, so try again without it */ + time_t t = time(NULL); + krb5_set_real_time(smb_krb5_context->krb5_context, t, 0); + } else { + /* not a skew problem */ + break; + } + } + + krb5_get_init_creds_opt_free(smb_krb5_context->krb5_context, krb_options); + + if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) { + (*error_string) = talloc_asprintf(credentials, "kinit for %s failed (%s)\n", + cli_credentials_get_principal(credentials, mem_ctx), + smb_get_krb5_error_message(smb_krb5_context->krb5_context, + ret, mem_ctx)); + talloc_free(mem_ctx); + return ret; + } + + /* cope with ticket being in the future due to clock skew */ + if ((unsigned)kdc_time > time(NULL)) { + time_t t = time(NULL); + int time_offset =(unsigned)kdc_time-t; + DEBUG(4,("Advancing clock by %d seconds to cope with clock skew\n", time_offset)); + krb5_set_real_time(smb_krb5_context->krb5_context, t + time_offset + 1, 0); + } + + if (ret == KRB5KDC_ERR_PREAUTH_FAILED && cli_credentials_wrong_password(credentials)) { + ret = kinit_to_ccache(parent_ctx, + credentials, + smb_krb5_context, + event_ctx, + ccache, obtained, + error_string); + } + + if (ret) { + (*error_string) = talloc_asprintf(credentials, "kinit for %s failed (%s)\n", + cli_credentials_get_principal(credentials, mem_ctx), + smb_get_krb5_error_message(smb_krb5_context->krb5_context, + ret, mem_ctx)); + talloc_free(mem_ctx); + return ret; + } + + DEBUG(10,("kinit for %s succeeded\n", + cli_credentials_get_principal(credentials, mem_ctx))); + + + talloc_free(mem_ctx); + return 0; +} + +static krb5_error_code free_keytab_container(struct keytab_container *ktc) +{ + return krb5_kt_close(ktc->smb_krb5_context->krb5_context, ktc->keytab); +} + +krb5_error_code smb_krb5_get_keytab_container(TALLOC_CTX *mem_ctx, + struct smb_krb5_context *smb_krb5_context, + krb5_keytab opt_keytab, + const char *keytab_name, + struct keytab_container **ktc) +{ + krb5_keytab keytab; + krb5_error_code ret; + + if (opt_keytab) { + keytab = opt_keytab; + } else { + ret = krb5_kt_resolve(smb_krb5_context->krb5_context, + keytab_name, &keytab); + if (ret) { + DEBUG(1,("failed to open krb5 keytab: %s\n", + smb_get_krb5_error_message( + smb_krb5_context->krb5_context, + ret, mem_ctx))); + return ret; + } + } + + *ktc = talloc(mem_ctx, struct keytab_container); + if (!*ktc) { + return ENOMEM; + } + + (*ktc)->smb_krb5_context = talloc_reference(*ktc, smb_krb5_context); + (*ktc)->keytab = keytab; + (*ktc)->password_based = false; + talloc_set_destructor(*ktc, free_keytab_container); + + return 0; +} + +/* + * Walk the keytab, looking for entries of this principal name, + * with KVNO other than current kvno -1. + * + * These entries are now stale, + * we only keep the current and previous entries around. + * + * Inspired by the code in Samba3 for 'use kerberos keytab'. + */ +krb5_error_code smb_krb5_remove_obsolete_keytab_entries(TALLOC_CTX *mem_ctx, + krb5_context context, + krb5_keytab keytab, + uint32_t num_principals, + krb5_principal *principals, + krb5_kvno kvno, + bool *found_previous, + const char **error_string) +{ + TALLOC_CTX *tmp_ctx; + krb5_error_code code; + krb5_kt_cursor cursor; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + *error_string = "Cannot allocate tmp_ctx"; + return ENOMEM; + } + + *found_previous = true; + + code = krb5_kt_start_seq_get(context, keytab, &cursor); + switch (code) { + case 0: + break; +#ifdef HEIM_ERR_OPNOTSUPP + case HEIM_ERR_OPNOTSUPP: +#endif + case ENOENT: + case KRB5_KT_END: + /* no point enumerating if there isn't anything here */ + code = 0; + goto done; + default: + *error_string = talloc_asprintf(mem_ctx, + "failed to open keytab for read of old entries: %s\n", + smb_get_krb5_error_message(context, code, mem_ctx)); + goto done; + } + + do { + krb5_kvno old_kvno = kvno - 1; + krb5_keytab_entry entry; + bool matched = false; + uint32_t i; + + code = krb5_kt_next_entry(context, keytab, &entry, &cursor); + if (code) { + break; + } + + for (i = 0; i < num_principals; i++) { + krb5_boolean ok; + + ok = smb_krb5_kt_compare(context, + &entry, + principals[i], + 0, + 0); + if (ok) { + matched = true; + break; + } + } + + if (!matched) { + /* + * Free the entry, it wasn't the one we were looking + * for anyway + */ + krb5_kt_free_entry(context, &entry); + /* Make sure we do not double free */ + ZERO_STRUCT(entry); + continue; + } + + /* + * Delete it, if it is not kvno - 1. + * + * Some keytab files store the kvno only in 8bits. Limit the + * compare to 8bits, so that we don't miss old keys and delete + * them. + */ + if ((entry.vno & 0xff) != (old_kvno & 0xff)) { + krb5_error_code rc; + + /* Release the enumeration. We are going to + * have to start this from the top again, + * because deletes during enumeration may not + * always be consistent. + * + * Also, the enumeration locks a FILE: keytab + */ + krb5_kt_end_seq_get(context, keytab, &cursor); + + code = krb5_kt_remove_entry(context, keytab, &entry); + krb5_kt_free_entry(context, &entry); + + /* Make sure we do not double free */ + ZERO_STRUCT(entry); + + /* Deleted: Restart from the top */ + rc = krb5_kt_start_seq_get(context, keytab, &cursor); + if (rc != 0) { + krb5_kt_free_entry(context, &entry); + + /* Make sure we do not double free */ + ZERO_STRUCT(entry); + + DEBUG(1, ("failed to restart enumeration of keytab: %s\n", + smb_get_krb5_error_message(context, + code, + tmp_ctx))); + + talloc_free(tmp_ctx); + return rc; + } + + if (code != 0) { + break; + } + + } else { + *found_previous = true; + } + + /* Free the entry, we don't need it any more */ + krb5_kt_free_entry(context, &entry); + /* Make sure we do not double free */ + ZERO_STRUCT(entry); + } while (code == 0); + + krb5_kt_end_seq_get(context, keytab, &cursor); + + switch (code) { + case 0: + break; + case ENOENT: + case KRB5_KT_END: + break; + default: + *error_string = talloc_asprintf(mem_ctx, + "failed in deleting old entries for principal: %s\n", + smb_get_krb5_error_message(context, + code, + mem_ctx)); + } + + code = 0; +done: + talloc_free(tmp_ctx); + return code; +} diff --git a/source4/auth/kerberos/krb5_init_context.c b/source4/auth/kerberos/krb5_init_context.c new file mode 100644 index 0000000..48cb256 --- /dev/null +++ b/source4/auth/kerberos/krb5_init_context.c @@ -0,0 +1,885 @@ +/* + Unix SMB/CIFS implementation. + Wrapper for krb5_init_context + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + Copyright (C) Andrew Tridgell 2005 + Copyright (C) Stefan Metzmacher 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/kerberos.h" +#include <tevent.h> +#include "auth/kerberos/kerberos.h" +#include "lib/socket/socket.h" +#include "lib/stream/packet.h" +#include "system/network.h" +#include "param/param.h" +#include "libcli/resolve/resolve.h" +#include "../lib/tsocket/tsocket.h" +#include "krb5_init_context.h" +#ifdef SAMBA4_USES_HEIMDAL +#include "../lib/dbwrap/dbwrap.h" +#include "../lib/dbwrap/dbwrap_rbt.h" +#include "../lib/util/util_tdb.h" +#include <krb5/send_to_kdc_plugin.h> +#endif + +/* + context structure for operations on cldap packets +*/ +struct smb_krb5_socket { + struct socket_context *sock; + + /* the fd event */ + struct tevent_fd *fde; + + NTSTATUS status; + DATA_BLOB request, reply; + + struct packet_context *packet; + + size_t partial_read; +#ifdef SAMBA4_USES_HEIMDAL + krb5_krbhst_info *hi; +#endif +}; + +static krb5_error_code smb_krb5_context_destroy(struct smb_krb5_context *ctx) +{ +#ifdef SAMBA4_USES_HEIMDAL + if (ctx->pvt_log_data) { + /* Otherwise krb5_free_context will try and close what we + * have already free()ed */ + krb5_set_warn_dest(ctx->krb5_context, NULL); + krb5_closelog(ctx->krb5_context, + (krb5_log_facility *)ctx->pvt_log_data); + } +#endif + krb5_free_context(ctx->krb5_context); + return 0; +} + +#ifdef SAMBA4_USES_HEIMDAL +/* We never close down the DEBUG system, and no need to unreference the use */ +static void smb_krb5_debug_close(void *private_data) { + return; +} +#endif + +#ifdef SAMBA4_USES_HEIMDAL +static void smb_krb5_debug_wrapper( +#ifdef HAVE_KRB5_ADDLOG_FUNC_NEED_CONTEXT + krb5_context ctx, +#endif /* HAVE_KRB5_ADDLOG_FUNC_NEED_CONTEXT */ + const char *timestr, const char *msg, void *private_data) +{ + DEBUGC(DBGC_KERBEROS, 3, ("Kerberos: %s\n", msg)); +} +#endif + +#ifdef SAMBA4_USES_HEIMDAL +/* + handle recv events on a smb_krb5 socket +*/ +static void smb_krb5_socket_recv(struct smb_krb5_socket *smb_krb5) +{ + TALLOC_CTX *tmp_ctx = talloc_new(smb_krb5); + DATA_BLOB blob; + size_t nread, dsize; + + smb_krb5->status = socket_pending(smb_krb5->sock, &dsize); + if (!NT_STATUS_IS_OK(smb_krb5->status)) { + talloc_free(tmp_ctx); + return; + } + + blob = data_blob_talloc(tmp_ctx, NULL, dsize); + if (blob.data == NULL && dsize != 0) { + smb_krb5->status = NT_STATUS_NO_MEMORY; + talloc_free(tmp_ctx); + return; + } + + smb_krb5->status = socket_recv(smb_krb5->sock, blob.data, blob.length, &nread); + if (!NT_STATUS_IS_OK(smb_krb5->status)) { + talloc_free(tmp_ctx); + return; + } + blob.length = nread; + + if (nread == 0) { + smb_krb5->status = NT_STATUS_UNEXPECTED_NETWORK_ERROR; + talloc_free(tmp_ctx); + return; + } + + DEBUG(4,("Received smb_krb5 packet of length %d\n", + (int)blob.length)); + + talloc_steal(smb_krb5, blob.data); + smb_krb5->reply = blob; + talloc_free(tmp_ctx); +} + +static NTSTATUS smb_krb5_full_packet(void *private_data, DATA_BLOB data) +{ + struct smb_krb5_socket *smb_krb5 = talloc_get_type(private_data, struct smb_krb5_socket); + talloc_steal(smb_krb5, data.data); + smb_krb5->reply = data; + smb_krb5->reply.length -= 4; + smb_krb5->reply.data += 4; + return NT_STATUS_OK; +} + +/* + handle request timeouts +*/ +static void smb_krb5_request_timeout(struct tevent_context *event_ctx, + struct tevent_timer *te, struct timeval t, + void *private_data) +{ + struct smb_krb5_socket *smb_krb5 = talloc_get_type(private_data, struct smb_krb5_socket); + DEBUG(5,("Timed out smb_krb5 packet\n")); + smb_krb5->status = NT_STATUS_IO_TIMEOUT; +} + +static void smb_krb5_error_handler(void *private_data, NTSTATUS status) +{ + struct smb_krb5_socket *smb_krb5 = talloc_get_type(private_data, struct smb_krb5_socket); + smb_krb5->status = status; +} + +/* + handle send events on a smb_krb5 socket +*/ +static void smb_krb5_socket_send(struct smb_krb5_socket *smb_krb5) +{ + NTSTATUS status; + + size_t len; + + len = smb_krb5->request.length; + status = socket_send(smb_krb5->sock, &smb_krb5->request, &len); + + if (!NT_STATUS_IS_OK(status)) return; + + TEVENT_FD_READABLE(smb_krb5->fde); + + TEVENT_FD_NOT_WRITEABLE(smb_krb5->fde); + return; +} + + +/* + handle fd events on a smb_krb5_socket +*/ +static void smb_krb5_socket_handler(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *private_data) +{ + struct smb_krb5_socket *smb_krb5 = talloc_get_type(private_data, struct smb_krb5_socket); + switch (smb_krb5->hi->proto) { + case KRB5_KRBHST_UDP: + if (flags & TEVENT_FD_READ) { + smb_krb5_socket_recv(smb_krb5); + return; + } + if (flags & TEVENT_FD_WRITE) { + smb_krb5_socket_send(smb_krb5); + return; + } + /* not reached */ + return; + case KRB5_KRBHST_TCP: + if (flags & TEVENT_FD_READ) { + packet_recv(smb_krb5->packet); + return; + } + if (flags & TEVENT_FD_WRITE) { + packet_queue_run(smb_krb5->packet); + return; + } + /* not reached */ + return; + case KRB5_KRBHST_HTTP: + /* can't happen */ + break; + } +} + +static krb5_error_code smb_krb5_send_and_recv_func_int(struct smb_krb5_context *smb_krb5_context, + struct tevent_context *ev, + krb5_krbhst_info *hi, + struct addrinfo *ai, + smb_krb5_send_to_kdc_func func, + void *data, + time_t timeout, + const krb5_data *send_buf, + krb5_data *recv_buf) +{ + krb5_error_code ret; + NTSTATUS status; + const char *name; + struct addrinfo *a; + struct smb_krb5_socket *smb_krb5; + + DATA_BLOB send_blob; + + TALLOC_CTX *frame = talloc_stackframe(); + if (frame == NULL) { + return ENOMEM; + } + + send_blob = data_blob_const(send_buf->data, send_buf->length); + + for (a = ai; a; a = a->ai_next) { + struct socket_address *remote_addr; + smb_krb5 = talloc(frame, struct smb_krb5_socket); + if (!smb_krb5) { + TALLOC_FREE(frame); + return ENOMEM; + } + smb_krb5->hi = hi; + + switch (a->ai_family) { + case PF_INET: + name = "ipv4"; + break; +#ifdef HAVE_IPV6 + case PF_INET6: + name = "ipv6"; + break; +#endif + default: + TALLOC_FREE(frame); + return EINVAL; + } + + status = NT_STATUS_INVALID_PARAMETER; + switch (hi->proto) { + case KRB5_KRBHST_UDP: + status = socket_create(smb_krb5, name, + SOCKET_TYPE_DGRAM, + &smb_krb5->sock, 0); + break; + case KRB5_KRBHST_TCP: + status = socket_create(smb_krb5, name, + SOCKET_TYPE_STREAM, + &smb_krb5->sock, 0); + break; + case KRB5_KRBHST_HTTP: + TALLOC_FREE(frame); + return EINVAL; + } + if (!NT_STATUS_IS_OK(status)) { + talloc_free(smb_krb5); + continue; + } + + remote_addr = socket_address_from_sockaddr(smb_krb5, a->ai_addr, a->ai_addrlen); + if (!remote_addr) { + talloc_free(smb_krb5); + continue; + } + + status = socket_connect_ev(smb_krb5->sock, NULL, remote_addr, 0, ev); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(smb_krb5); + continue; + } + + /* Setup the FDE, start listening for read events + * from the start (otherwise we may miss a socket + * drop) and mark as AUTOCLOSE along with the fde */ + + /* Ths is equivilant to EVENT_FD_READABLE(smb_krb5->fde) */ + smb_krb5->fde = tevent_add_fd(ev, smb_krb5->sock, + socket_get_fd(smb_krb5->sock), + TEVENT_FD_READ, + smb_krb5_socket_handler, smb_krb5); + /* its now the job of the event layer to close the socket */ + tevent_fd_set_close_fn(smb_krb5->fde, socket_tevent_fd_close_fn); + socket_set_flags(smb_krb5->sock, SOCKET_FLAG_NOCLOSE); + + tevent_add_timer(ev, smb_krb5, + timeval_current_ofs(timeout, 0), + smb_krb5_request_timeout, smb_krb5); + + smb_krb5->status = NT_STATUS_OK; + smb_krb5->reply = data_blob(NULL, 0); + + switch (hi->proto) { + case KRB5_KRBHST_UDP: + TEVENT_FD_WRITEABLE(smb_krb5->fde); + smb_krb5->request = send_blob; + break; + case KRB5_KRBHST_TCP: + + smb_krb5->packet = packet_init(smb_krb5); + if (smb_krb5->packet == NULL) { + talloc_free(smb_krb5); + return ENOMEM; + } + packet_set_private(smb_krb5->packet, smb_krb5); + packet_set_socket(smb_krb5->packet, smb_krb5->sock); + packet_set_callback(smb_krb5->packet, smb_krb5_full_packet); + packet_set_full_request(smb_krb5->packet, packet_full_request_u32); + packet_set_error_handler(smb_krb5->packet, smb_krb5_error_handler); + packet_set_event_context(smb_krb5->packet, ev); + packet_set_fde(smb_krb5->packet, smb_krb5->fde); + + smb_krb5->request = data_blob_talloc(smb_krb5, NULL, send_blob.length + 4); + RSIVAL(smb_krb5->request.data, 0, send_blob.length); + memcpy(smb_krb5->request.data+4, send_blob.data, send_blob.length); + packet_send(smb_krb5->packet, smb_krb5->request); + break; + case KRB5_KRBHST_HTTP: + TALLOC_FREE(frame); + return EINVAL; + } + while ((NT_STATUS_IS_OK(smb_krb5->status)) && !smb_krb5->reply.length) { + if (tevent_loop_once(ev) != 0) { + TALLOC_FREE(frame); + return EINVAL; + } + + if (func) { + /* After each and every event loop, reset the + * send_to_kdc pointers to what they were when + * we entered this loop. That way, if a + * nested event has invalidated them, we put + * it back before we return to the heimdal + * code */ + ret = smb_krb5_set_send_to_kdc_func(smb_krb5_context, + NULL, /* send_to_realm */ + func, + data); + if (ret != 0) { + TALLOC_FREE(frame); + return ret; + } + } + } + if (NT_STATUS_EQUAL(smb_krb5->status, NT_STATUS_IO_TIMEOUT)) { + talloc_free(smb_krb5); + continue; + } + + if (!NT_STATUS_IS_OK(smb_krb5->status)) { + struct tsocket_address *addr = socket_address_to_tsocket_address(smb_krb5, remote_addr); + const char *addr_string = NULL; + if (addr) { + addr_string = tsocket_address_inet_addr_string(addr, smb_krb5); + } else { + addr_string = NULL; + } + DEBUG(2,("Error reading smb_krb5 reply packet: %s from %s\n", nt_errstr(smb_krb5->status), + addr_string)); + talloc_free(smb_krb5); + continue; + } + + ret = krb5_data_copy(recv_buf, smb_krb5->reply.data, smb_krb5->reply.length); + if (ret) { + TALLOC_FREE(frame); + return ret; + } + talloc_free(smb_krb5); + + break; + } + TALLOC_FREE(frame); + if (a) { + return 0; + } + return KRB5_KDC_UNREACH; +} + +krb5_error_code smb_krb5_send_and_recv_func(struct smb_krb5_context *smb_krb5_context, + void *data, + krb5_krbhst_info *hi, + time_t timeout, + const krb5_data *send_buf, + krb5_data *recv_buf) +{ + krb5_error_code ret; + struct addrinfo *ai; + + struct tevent_context *ev; + TALLOC_CTX *frame = talloc_stackframe(); + if (frame == NULL) { + return ENOMEM; + } + + if (data == NULL) { + /* If no event context was available, then create one for this loop */ + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + TALLOC_FREE(frame); + return ENOMEM; + } + } else { + ev = talloc_get_type_abort(data, struct tevent_context); + } + + ret = krb5_krbhst_get_addrinfo(smb_krb5_context->krb5_context, hi, &ai); + if (ret) { + TALLOC_FREE(frame); + return ret; + } + + ret = smb_krb5_send_and_recv_func_int(smb_krb5_context, + ev, hi, ai, + smb_krb5_send_and_recv_func, + data, timeout, send_buf, recv_buf); + TALLOC_FREE(frame); + return ret; +} + +krb5_error_code smb_krb5_send_and_recv_func_forced_tcp(struct smb_krb5_context *smb_krb5_context, + struct addrinfo *ai, + time_t timeout, + const krb5_data *send_buf, + krb5_data *recv_buf) +{ + krb5_error_code k5ret; + krb5_krbhst_info hi = { + .proto = KRB5_KRBHST_TCP, + }; + struct tevent_context *ev; + TALLOC_CTX *frame = talloc_stackframe(); + if (frame == NULL) { + return ENOMEM; + } + + /* no event context is passed in, create one for this loop */ + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + TALLOC_FREE(frame); + return ENOMEM; + } + + /* No need to pass in send_and_recv functions, we won't nest on this private event loop */ + k5ret = smb_krb5_send_and_recv_func_int(smb_krb5_context, ev, &hi, ai, NULL, NULL, + timeout, send_buf, recv_buf); + TALLOC_FREE(frame); + return k5ret; +} + +static struct db_context *smb_krb5_plugin_db; + +struct smb_krb5_send_to_kdc_state { + intptr_t key_ptr; + struct smb_krb5_context *smb_krb5_context; + smb_krb5_send_to_realm_func send_to_realm; + smb_krb5_send_to_kdc_func send_to_kdc; + void *private_data; +}; + +static int smb_krb5_send_to_kdc_state_destructor(struct smb_krb5_send_to_kdc_state *state) +{ + TDB_DATA key = make_tdb_data((uint8_t *)&state->key_ptr, sizeof(state->key_ptr)); + struct db_record *rec = NULL; + NTSTATUS status; + + rec = dbwrap_fetch_locked(smb_krb5_plugin_db, state, key); + if (rec == NULL) { + return 0; + } + + status = dbwrap_record_delete(rec); + TALLOC_FREE(rec); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + + state->smb_krb5_context = NULL; + return 0; +} + +krb5_error_code smb_krb5_set_send_to_kdc_func(struct smb_krb5_context *smb_krb5_context, + smb_krb5_send_to_realm_func send_to_realm, + smb_krb5_send_to_kdc_func send_to_kdc, + void *private_data) +{ + intptr_t key_ptr = (intptr_t)smb_krb5_context->krb5_context; + TDB_DATA key = make_tdb_data((uint8_t *)&key_ptr, sizeof(key_ptr)); + intptr_t value_ptr = (intptr_t)NULL; + TDB_DATA value = make_tdb_data(NULL, 0); + struct db_record *rec = NULL; + struct smb_krb5_send_to_kdc_state *state = NULL; + NTSTATUS status; + + rec = dbwrap_fetch_locked(smb_krb5_plugin_db, smb_krb5_context, key); + if (rec == NULL) { + return ENOMEM; + } + + value = dbwrap_record_get_value(rec); + if (value.dsize != 0) { + SMB_ASSERT(value.dsize == sizeof(value_ptr)); + memcpy(&value_ptr, value.dptr, sizeof(value_ptr)); + state = talloc_get_type_abort((const void *)value_ptr, + struct smb_krb5_send_to_kdc_state); + if (send_to_realm == NULL && send_to_kdc == NULL) { + status = dbwrap_record_delete(rec); + TALLOC_FREE(rec); + if (!NT_STATUS_IS_OK(status)) { + return EINVAL; + } + return 0; + } + state->send_to_realm = send_to_realm; + state->send_to_kdc = send_to_kdc; + state->private_data = private_data; + TALLOC_FREE(rec); + return 0; + } + + if (send_to_kdc == NULL && send_to_realm == NULL) { + TALLOC_FREE(rec); + return 0; + } + + state = talloc_zero(smb_krb5_context, + struct smb_krb5_send_to_kdc_state); + if (state == NULL) { + TALLOC_FREE(rec); + return ENOMEM; + } + state->key_ptr = key_ptr; + state->smb_krb5_context = smb_krb5_context; + state->send_to_realm = send_to_realm; + state->send_to_kdc = send_to_kdc; + state->private_data = private_data; + + value_ptr = (intptr_t)state; + value = make_tdb_data((uint8_t *)&value_ptr, sizeof(value_ptr)); + + status = dbwrap_record_store(rec, value, TDB_INSERT); + TALLOC_FREE(rec); + if (!NT_STATUS_IS_OK(status)) { + return EINVAL; + } + talloc_set_destructor(state, smb_krb5_send_to_kdc_state_destructor); + + return 0; +} + +static krb5_error_code smb_krb5_plugin_init(krb5_context context, void **pctx) +{ + *pctx = NULL; + return 0; +} + +static void smb_krb5_plugin_fini(void *ctx) +{ +} + +static void smb_krb5_send_to_kdc_state_parser(TDB_DATA key, TDB_DATA value, + void *private_data) +{ + struct smb_krb5_send_to_kdc_state **state = + (struct smb_krb5_send_to_kdc_state **)private_data; + intptr_t value_ptr; + + SMB_ASSERT(value.dsize == sizeof(value_ptr)); + memcpy(&value_ptr, value.dptr, sizeof(value_ptr)); + *state = talloc_get_type_abort((const void *)value_ptr, + struct smb_krb5_send_to_kdc_state); +} + +static struct smb_krb5_send_to_kdc_state * +smb_krb5_send_to_kdc_get_state(krb5_context context) +{ + intptr_t key_ptr = (intptr_t)context; + TDB_DATA key = make_tdb_data((uint8_t *)&key_ptr, sizeof(key_ptr)); + struct smb_krb5_send_to_kdc_state *state = NULL; + NTSTATUS status; + + status = dbwrap_parse_record(smb_krb5_plugin_db, key, + smb_krb5_send_to_kdc_state_parser, + &state); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + + return state; +} + +static krb5_error_code smb_krb5_plugin_send_to_kdc(krb5_context context, + void *ctx, + krb5_krbhst_info *ho, + time_t timeout, + const krb5_data *in, + krb5_data *out) +{ + struct smb_krb5_send_to_kdc_state *state = NULL; + + state = smb_krb5_send_to_kdc_get_state(context); + if (state == NULL) { + return KRB5_PLUGIN_NO_HANDLE; + } + + if (state->send_to_kdc == NULL) { + return KRB5_PLUGIN_NO_HANDLE; + } + + return state->send_to_kdc(state->smb_krb5_context, + state->private_data, + ho, timeout, in, out); +} + +static krb5_error_code smb_krb5_plugin_send_to_realm(krb5_context context, + void *ctx, + krb5_const_realm realm, + time_t timeout, + const krb5_data *in, + krb5_data *out) +{ + struct smb_krb5_send_to_kdc_state *state = NULL; + + state = smb_krb5_send_to_kdc_get_state(context); + if (state == NULL) { + return KRB5_PLUGIN_NO_HANDLE; + } + + if (state->send_to_realm == NULL) { + return KRB5_PLUGIN_NO_HANDLE; + } + + return state->send_to_realm(state->smb_krb5_context, + state->private_data, + realm, timeout, in, out); +} + +static krb5plugin_send_to_kdc_ftable smb_krb5_plugin_ftable = { + KRB5_PLUGIN_SEND_TO_KDC_VERSION_2, + smb_krb5_plugin_init, + smb_krb5_plugin_fini, + smb_krb5_plugin_send_to_kdc, + smb_krb5_plugin_send_to_realm +}; +#endif + +krb5_error_code +smb_krb5_init_context_basic(TALLOC_CTX *tmp_ctx, + struct loadparm_context *lp_ctx, + krb5_context *_krb5_context) +{ + krb5_error_code ret; +#ifdef SAMBA4_USES_HEIMDAL + char **config_files; + const char *config_file, *realm; +#endif + krb5_context krb5_ctx; + + ret = smb_krb5_init_context_common(&krb5_ctx); + if (ret) { + return ret; + } + + /* The MIT Kerberos build relies on using the system krb5.conf file. + * If you really want to use another file please set KRB5_CONFIG + * accordingly. */ +#ifdef SAMBA4_USES_HEIMDAL + config_file = lpcfg_config_path(tmp_ctx, lp_ctx, "krb5.conf"); + if (!config_file) { + krb5_free_context(krb5_ctx); + return ENOMEM; + } + + /* Use our local krb5.conf file by default */ + ret = krb5_prepend_config_files_default(config_file, &config_files); + if (ret) { + DEBUG(1,("krb5_prepend_config_files_default failed (%s)\n", + smb_get_krb5_error_message(krb5_ctx, ret, tmp_ctx))); + krb5_free_context(krb5_ctx); + return ret; + } + + ret = krb5_set_config_files(krb5_ctx, config_files); + krb5_free_config_files(config_files); + if (ret) { + DEBUG(1,("krb5_set_config_files failed (%s)\n", + smb_get_krb5_error_message(krb5_ctx, ret, tmp_ctx))); + krb5_free_context(krb5_ctx); + return ret; + } + + /* + * This is already called in smb_krb5_init_context_common(), + * but krb5_set_config_files() may resets it. + */ + krb5_set_dns_canonicalize_hostname(krb5_ctx, false); + + realm = lpcfg_realm(lp_ctx); + if (realm != NULL) { + ret = krb5_set_default_realm(krb5_ctx, realm); + if (ret) { + DEBUG(1,("krb5_set_default_realm failed (%s)\n", + smb_get_krb5_error_message(krb5_ctx, ret, tmp_ctx))); + krb5_free_context(krb5_ctx); + return ret; + } + } + + if (smb_krb5_plugin_db == NULL) { + /* + * while krb5_plugin_register() takes a krb5_context, + * plugins are registered into a global list, so + * we only do that once + * + * We maintain a separate dispatch table for per + * krb5_context state. + */ + ret = krb5_plugin_register(krb5_ctx, PLUGIN_TYPE_DATA, + KRB5_PLUGIN_SEND_TO_KDC, + &smb_krb5_plugin_ftable); + if (ret) { + DEBUG(1,("krb5_plugin_register(KRB5_PLUGIN_SEND_TO_KDC) failed (%s)\n", + smb_get_krb5_error_message(krb5_ctx, ret, tmp_ctx))); + krb5_free_context(krb5_ctx); + return ret; + } + smb_krb5_plugin_db = db_open_rbt(NULL); + if (smb_krb5_plugin_db == NULL) { + DEBUG(1,("db_open_rbt() failed\n")); + krb5_free_context(krb5_ctx); + return ENOMEM; + } + } +#endif + *_krb5_context = krb5_ctx; + return 0; +} + +krb5_error_code smb_krb5_init_context(void *parent_ctx, + struct loadparm_context *lp_ctx, + struct smb_krb5_context **smb_krb5_context) +{ + krb5_error_code ret; + TALLOC_CTX *tmp_ctx; + krb5_context kctx; +#ifdef SAMBA4_USES_HEIMDAL + krb5_log_facility *logf; +#endif + + tmp_ctx = talloc_new(parent_ctx); + *smb_krb5_context = talloc_zero(tmp_ctx, struct smb_krb5_context); + + if (!*smb_krb5_context || !tmp_ctx) { + talloc_free(tmp_ctx); + return ENOMEM; + } + + ret = smb_krb5_init_context_basic(tmp_ctx, lp_ctx, &kctx); + if (ret) { + DEBUG(1,("smb_krb5_context_init_basic failed (%s)\n", + error_message(ret))); + talloc_free(tmp_ctx); + return ret; + } + (*smb_krb5_context)->krb5_context = kctx; + + talloc_set_destructor(*smb_krb5_context, smb_krb5_context_destroy); + +#ifdef SAMBA4_USES_HEIMDAL + /* TODO: Should we have a different name here? */ + ret = krb5_initlog(kctx, "Samba", &logf); + + if (ret) { + DEBUG(1,("krb5_initlog failed (%s)\n", + smb_get_krb5_error_message(kctx, ret, tmp_ctx))); + talloc_free(tmp_ctx); + return ret; + } + (*smb_krb5_context)->pvt_log_data = logf; + + ret = krb5_addlog_func(kctx, logf, 0 /* min */, -1 /* max */, + smb_krb5_debug_wrapper, + smb_krb5_debug_close, NULL); + if (ret) { + DEBUG(1,("krb5_addlog_func failed (%s)\n", + smb_get_krb5_error_message(kctx, ret, tmp_ctx))); + talloc_free(tmp_ctx); + return ret; + } + krb5_set_warn_dest(kctx, logf); +#endif + talloc_steal(parent_ctx, *smb_krb5_context); + talloc_free(tmp_ctx); + + return 0; +} + +#ifdef SAMBA4_USES_HEIMDAL +krb5_error_code smb_krb5_context_set_event_ctx(struct smb_krb5_context *smb_krb5_context, + struct tevent_context *ev, + struct tevent_context **previous_ev) +{ + int ret; + if (!ev) { + return EINVAL; + } + + *previous_ev = smb_krb5_context->current_ev; + + smb_krb5_context->current_ev = talloc_reference(smb_krb5_context, ev); + if (!smb_krb5_context->current_ev) { + return ENOMEM; + } + + /* Set use of our socket lib */ + ret = smb_krb5_set_send_to_kdc_func(smb_krb5_context, + NULL, /* send_to_realm */ + smb_krb5_send_and_recv_func, + ev); + if (ret) { + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + DEBUG(1,("smb_krb5_set_send_recv_func failed (%s)\n", + smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, tmp_ctx))); + talloc_free(tmp_ctx); + talloc_unlink(smb_krb5_context, smb_krb5_context->current_ev); + smb_krb5_context->current_ev = NULL; + return ret; + } + return 0; +} + +krb5_error_code smb_krb5_context_remove_event_ctx(struct smb_krb5_context *smb_krb5_context, + struct tevent_context *previous_ev, + struct tevent_context *ev) +{ + int ret; + talloc_unlink(smb_krb5_context, ev); + /* If there was a mismatch with things happening on a stack, then don't wipe things */ + smb_krb5_context->current_ev = previous_ev; + /* Set use of our socket lib */ + ret = smb_krb5_set_send_to_kdc_func(smb_krb5_context, + NULL, /* send_to_realm */ + smb_krb5_send_and_recv_func, + previous_ev); + if (ret) { + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + DEBUG(1,("smb_krb5_set_send_recv_func failed (%s)\n", + smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, tmp_ctx))); + talloc_free(tmp_ctx); + return ret; + } + return 0; +} +#endif diff --git a/source4/auth/kerberos/krb5_init_context.h b/source4/auth/kerberos/krb5_init_context.h new file mode 100644 index 0000000..c7f7cfd --- /dev/null +++ b/source4/auth/kerberos/krb5_init_context.h @@ -0,0 +1,79 @@ +/* + Unix SMB/CIFS implementation. + simple kerberos5 routines for active directory + Copyright (C) Andrew Bartlett 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _KRB5_INIT_CONTEXT_H_ +#define _KRB5_INIT_CONTEXT_H_ + +struct smb_krb5_context { + krb5_context krb5_context; + void *pvt_log_data; + struct tevent_context *current_ev; +}; + +struct tevent_context; +struct loadparm_context; + +krb5_error_code +smb_krb5_init_context_basic(TALLOC_CTX *tmp_ctx, + struct loadparm_context *lp_ctx, + krb5_context *_krb5_context); + +krb5_error_code smb_krb5_init_context(void *parent_ctx, + struct loadparm_context *lp_ctx, + struct smb_krb5_context **smb_krb5_context); + +#ifdef SAMBA4_USES_HEIMDAL +typedef krb5_error_code (*smb_krb5_send_to_realm_func)(struct smb_krb5_context *smb_krb5_context, + void *, + krb5_const_realm realm, + time_t, + const krb5_data *, + krb5_data *); +typedef krb5_error_code (*smb_krb5_send_to_kdc_func)(struct smb_krb5_context *smb_krb5_context, + void *, + krb5_krbhst_info *, + time_t, + const krb5_data *, + krb5_data *); + +krb5_error_code smb_krb5_set_send_to_kdc_func(struct smb_krb5_context *smb_krb5_context, + smb_krb5_send_to_realm_func send_to_realm, + smb_krb5_send_to_kdc_func send_to_kdc, + void *private_data); + +krb5_error_code smb_krb5_send_and_recv_func(struct smb_krb5_context *smb_krb5_context, + void *data, + krb5_krbhst_info *hi, + time_t timeout, + const krb5_data *send_buf, + krb5_data *recv_buf); +krb5_error_code smb_krb5_send_and_recv_func_forced_tcp(struct smb_krb5_context *smb_krb5_context, + struct addrinfo *ai, + time_t timeout, + const krb5_data *send_buf, + krb5_data *recv_buf); +krb5_error_code smb_krb5_context_set_event_ctx(struct smb_krb5_context *smb_krb5_context, + struct tevent_context *ev, + struct tevent_context **previous_ev); +krb5_error_code smb_krb5_context_remove_event_ctx(struct smb_krb5_context *smb_krb5_context, + struct tevent_context *previous_ev, + struct tevent_context *ev); +#endif + +#endif /* _KRB5_INIT_CONTEXT_H_ */ diff --git a/source4/auth/kerberos/srv_keytab.c b/source4/auth/kerberos/srv_keytab.c new file mode 100644 index 0000000..52e1e22 --- /dev/null +++ b/source4/auth/kerberos/srv_keytab.c @@ -0,0 +1,406 @@ +/* + Unix SMB/CIFS implementation. + + Kerberos utility functions + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/** + * @file srv_keytab.c + * + * @brief Kerberos keytab utility functions + * + */ + +#include "includes.h" +#include "system/kerberos.h" +#include "auth/credentials/credentials.h" +#include "auth/kerberos/kerberos.h" +#include "auth/kerberos/kerberos_util.h" +#include "auth/kerberos/kerberos_srv_keytab.h" + +static void keytab_principals_free(krb5_context context, + uint32_t num_principals, + krb5_principal *set) +{ + uint32_t i; + + for (i = 0; i < num_principals; i++) { + krb5_free_principal(context, set[i]); + } +} + +static krb5_error_code keytab_add_keys(TALLOC_CTX *parent_ctx, + uint32_t num_principals, + krb5_principal *principals, + krb5_principal salt_princ, + int kvno, + const char *password_s, + krb5_context context, + krb5_enctype *enctypes, + krb5_keytab keytab, + const char **error_string) +{ + unsigned int i, p; + krb5_error_code ret; + krb5_data password; + char *unparsed; + + password.data = discard_const_p(char, password_s); + password.length = strlen(password_s); + + for (i = 0; enctypes[i]; i++) { + krb5_keytab_entry entry; + + ZERO_STRUCT(entry); + + ret = smb_krb5_create_key_from_string(context, + salt_princ, + NULL, + &password, + enctypes[i], + KRB5_KT_KEY(&entry)); + if (ret != 0) { + *error_string = talloc_strdup(parent_ctx, + "Failed to create key from string"); + return ret; + } + + entry.vno = kvno; + + for (p = 0; p < num_principals; p++) { + unparsed = NULL; + entry.principal = principals[p]; + ret = krb5_kt_add_entry(context, keytab, &entry); + if (ret != 0) { + char *k5_error_string = + smb_get_krb5_error_message(context, + ret, NULL); + krb5_unparse_name(context, + principals[p], &unparsed); + *error_string = talloc_asprintf(parent_ctx, + "Failed to add enctype %d entry for " + "%s(kvno %d) to keytab: %s\n", + (int)enctypes[i], unparsed, + kvno, k5_error_string); + + free(unparsed); + talloc_free(k5_error_string); + krb5_free_keyblock_contents(context, + KRB5_KT_KEY(&entry)); + return ret; + } + + DEBUG(5, ("Added key (kvno %d) to keytab (enctype %d)\n", + kvno, (int)enctypes[i])); + } + krb5_free_keyblock_contents(context, KRB5_KT_KEY(&entry)); + } + return 0; +} + +static krb5_error_code create_keytab(TALLOC_CTX *parent_ctx, + const char *samAccountName, + const char *realm, + const char *saltPrincipal, + int kvno, + const char *new_secret, + const char *old_secret, + uint32_t supp_enctypes, + uint32_t num_principals, + krb5_principal *principals, + krb5_context context, + krb5_keytab keytab, + bool add_old, + const char **perror_string) +{ + krb5_error_code ret; + krb5_principal salt_princ = NULL; + krb5_enctype *enctypes; + TALLOC_CTX *mem_ctx; + const char *error_string = NULL; + + if (!new_secret) { + /* There is no password here, so nothing to do */ + return 0; + } + + mem_ctx = talloc_new(parent_ctx); + if (!mem_ctx) { + *perror_string = talloc_strdup(parent_ctx, + "unable to allocate tmp_ctx for create_keytab"); + return ENOMEM; + } + + /* The salt used to generate these entries may be different however, + * fetch that */ + ret = krb5_parse_name(context, saltPrincipal, &salt_princ); + if (ret) { + *perror_string = smb_get_krb5_error_message(context, + ret, + parent_ctx); + talloc_free(mem_ctx); + return ret; + } + + ret = ms_suptypes_to_ietf_enctypes(mem_ctx, supp_enctypes, &enctypes); + if (ret) { + *perror_string = talloc_asprintf(parent_ctx, + "create_keytab: generating list of " + "encryption types failed (%s)\n", + smb_get_krb5_error_message(context, + ret, mem_ctx)); + goto done; + } + + ret = keytab_add_keys(mem_ctx, + num_principals, + principals, + salt_princ, kvno, new_secret, + context, enctypes, keytab, &error_string); + if (ret) { + *perror_string = talloc_steal(parent_ctx, error_string); + goto done; + } + + if (old_secret && add_old && kvno != 0) { + ret = keytab_add_keys(mem_ctx, + num_principals, + principals, + salt_princ, kvno - 1, old_secret, + context, enctypes, keytab, &error_string); + if (ret) { + *perror_string = talloc_steal(parent_ctx, error_string); + } + } + +done: + krb5_free_principal(context, salt_princ); + talloc_free(mem_ctx); + return ret; +} + +/** + * @brief Update a Kerberos keytab and removes any obsolete keytab entries. + * + * If the keytab does not exist, this function will create one. + * + * @param[in] parent_ctx Talloc memory context + * @param[in] context Kerberos context + * @param[in] keytab_name Keytab to open + * @param[in] samAccountName User account to update + * @param[in] realm Kerberos realm + * @param[in] SPNs Service principal names to update + * @param[in] num_SPNs Length of SPNs + * @param[in] saltPrincipal Salt used for AES encryption. + * Required, unless delete_all_kvno is set. + * @param[in] old_secret Old password + * @param[in] new_secret New password + * @param[in] kvno Current key version number + * @param[in] supp_enctypes msDS-SupportedEncryptionTypes bit-field + * @param[in] delete_all_kvno Removes all obsolete entries, without + * recreating the keytab. + * @param[out] _keytab If supplied, returns the keytab + * @param[out] perror_string Error string on failure + * + * @return 0 on success, errno on failure + */ +krb5_error_code smb_krb5_update_keytab(TALLOC_CTX *parent_ctx, + krb5_context context, + const char *keytab_name, + const char *samAccountName, + const char *realm, + const char **SPNs, + int num_SPNs, + const char *saltPrincipal, + const char *new_secret, + const char *old_secret, + int kvno, + uint32_t supp_enctypes, + bool delete_all_kvno, + krb5_keytab *_keytab, + const char **perror_string) +{ + krb5_keytab keytab; + krb5_error_code ret; + bool found_previous = false; + TALLOC_CTX *tmp_ctx; + krb5_principal *principals = NULL; + uint32_t num_principals = 0; + char *upper_realm; + const char *error_string = NULL; + + if (keytab_name == NULL) { + return ENOENT; + } + + ret = krb5_kt_resolve(context, keytab_name, &keytab); + if (ret) { + *perror_string = smb_get_krb5_error_message(context, + ret, parent_ctx); + return ret; + } + + DEBUG(5, ("Opened keytab %s\n", keytab_name)); + + tmp_ctx = talloc_new(parent_ctx); + if (!tmp_ctx) { + *perror_string = talloc_strdup(parent_ctx, + "Failed to allocate memory context"); + return ENOMEM; + } + + upper_realm = strupper_talloc(tmp_ctx, realm); + if (upper_realm == NULL) { + *perror_string = talloc_strdup(parent_ctx, + "Cannot allocate memory to upper case realm"); + talloc_free(tmp_ctx); + return ENOMEM; + } + + ret = smb_krb5_create_principals_array(tmp_ctx, + context, + samAccountName, + upper_realm, + num_SPNs, + SPNs, + &num_principals, + &principals, + &error_string); + if (ret != 0) { + *perror_string = talloc_asprintf(parent_ctx, + "Failed to load principals from ldb message: %s\n", + error_string); + goto done; + } + + ret = smb_krb5_remove_obsolete_keytab_entries(tmp_ctx, + context, + keytab, + num_principals, + principals, + kvno, + &found_previous, + &error_string); + if (ret != 0) { + *perror_string = talloc_asprintf(parent_ctx, + "Failed to remove old principals from keytab: %s\n", + error_string); + goto done; + } + + if (!delete_all_kvno) { + /* Create a new keytab. If during the cleanout we found + * entries for kvno -1, then don't try and duplicate them. + * Otherwise, add kvno, and kvno -1 */ + if (saltPrincipal == NULL) { + *perror_string = talloc_strdup(parent_ctx, + "No saltPrincipal provided"); + ret = EINVAL; + goto done; + } + + ret = create_keytab(tmp_ctx, + samAccountName, upper_realm, saltPrincipal, + kvno, new_secret, old_secret, + supp_enctypes, + num_principals, + principals, + context, keytab, + found_previous ? false : true, + &error_string); + if (ret) { + *perror_string = talloc_steal(parent_ctx, error_string); + } + } + + if (ret == 0 && _keytab != NULL) { + /* caller wants the keytab handle back */ + *_keytab = keytab; + } + +done: + keytab_principals_free(context, num_principals, principals); + if (ret != 0 || _keytab == NULL) { + krb5_kt_close(context, keytab); + } + talloc_free(tmp_ctx); + return ret; +} + +/** + * @brief Wrapper around smb_krb5_update_keytab() for creating an in-memory keytab + * + * @param[in] parent_ctx Talloc memory context + * @param[in] context Kerberos context + * @param[in] new_secret New password + * @param[in] samAccountName User account to update + * @param[in] realm Kerberos realm + * @param[in] salt_principal Salt used for AES encryption. + * Required, unless delete_all_kvno is set. + * @param[in] kvno Current key version number + * @param[out] keytab If supplied, returns the keytab + * @param[out] keytab_name Returns the created keytab name + * + * @return 0 on success, errno on failure + */ +krb5_error_code smb_krb5_create_memory_keytab(TALLOC_CTX *parent_ctx, + krb5_context context, + const char *new_secret, + const char *samAccountName, + const char *realm, + const char *salt_principal, + int kvno, + krb5_keytab *keytab, + const char **keytab_name) +{ + krb5_error_code ret; + TALLOC_CTX *mem_ctx = talloc_new(parent_ctx); + const char *rand_string; + const char *error_string = NULL; + if (!mem_ctx) { + return ENOMEM; + } + + rand_string = generate_random_str(mem_ctx, 16); + if (!rand_string) { + talloc_free(mem_ctx); + return ENOMEM; + } + + *keytab_name = talloc_asprintf(mem_ctx, "MEMORY:%s", rand_string); + if (*keytab_name == NULL) { + talloc_free(mem_ctx); + return ENOMEM; + } + + ret = smb_krb5_update_keytab(mem_ctx, context, + *keytab_name, samAccountName, realm, + NULL, 0, salt_principal, new_secret, NULL, + kvno, ENC_ALL_TYPES, + false, keytab, &error_string); + if (ret == 0) { + talloc_steal(parent_ctx, *keytab_name); + } else { + DEBUG(0, ("Failed to create in-memory keytab: %s\n", + error_string)); + *keytab_name = NULL; + } + talloc_free(mem_ctx); + return ret; +} diff --git a/source4/auth/kerberos/wscript_build b/source4/auth/kerberos/wscript_build new file mode 100644 index 0000000..5f14bed --- /dev/null +++ b/source4/auth/kerberos/wscript_build @@ -0,0 +1,26 @@ +#!/usr/bin/env python + +bld.SAMBA_SUBSYSTEM('KRB_INIT_CTX', + source='krb5_init_context.c', + deps='gssapi krb5samba dbwrap samba-util' + ) + +bld.SAMBA_LIBRARY('authkrb5', + source='kerberos_pac.c', + autoproto='proto.h', + public_deps='ndr-krb5pac krb5samba samba_socket LIBCLI_RESOLVE asn1', + deps='common_auth tevent LIBPACKET ndr ldb krb5samba KRB_INIT_CTX KRB5_PAC samba-errors', + private_library=True + ) + +bld.SAMBA_SUBSYSTEM('KERBEROS_UTIL', + autoproto='kerberos_util.h', + source='kerberos_util.c', + deps='authkrb5 krb5samba com_err CREDENTIALS_KRB5', + ) + +bld.SAMBA_SUBSYSTEM('KERBEROS_SRV_KEYTAB', + autoproto='kerberos_srv_keytab.h', + source='srv_keytab.c', + deps='authkrb5', + ) |