Implementing RBCD

classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|

Implementing RBCD

Isaac Boukris
Hi,

I'm working on implementing resource-based-constrained-delegation
functionality in MIT code, which moves the authorization attributes
from the impersonating account to the target account and essentially
allows for cross-realm S4U2Proxy.

I've uploaded some network traces of RBCD exchange to:
https://github.com/iboukris/S4U/tree/master/logs/rbcd

Here are the changes to the flow, compared to the old protocol as far
as I understand, based partly on MS-SFU but mostly on observations
(including some of my own interpretation).

Intro note: service-a is the impersonating service, and service-b is
the target one. Also note, a new padata PA-PAC-OPTIONS was added, for
the client to signal that it supports rbcd (in the relevant requests),
and for the KDC to ack on it via returned encrypted padata.

1) First, service-a acquires a ticket on behalf of the client to self
(regular or via S4U2Self). According to MS-SFU 3.1.5.2.1 the ticket is
no longer required to be forwardable, but in that case, conditions may
apply:
a) the service has to check whether the impersonated client is
sensitive and not allowed to be delegated, by checking the relevant
bit in UserAccountControl field in the KERB_VALIDATION_INFO inside the
PAC. If the client is sensitive the service must fail the request.
b) otherwise, if the ticket is not forwardable but the client is
nonsensitive then the service SHOULD locate a DS_BEHAVIOR_WIN2012 DC
for the following S4U2Proxy request (presumably, a KDC that does not
support rbcd will reject a non-forwardable ticket).

[notes: both these requirements are challenging:
The first would involve full decoding of the PAC, while I think it
could be a good idea to have it in krb5 library (for other purposes as
well), I only have a vague idea of what NDR decoding actually means,
and as far as I remember Greg was not fond of this idea in the past.
However I think it may not be a must, since the next KDC MUST do the
same check anyway (per MS-SFU 3.2.5.2) and we could also check the
reply encrypted-padata to make sure the KDC supports rbcd.
The second requirement involves an RPC call (as per MS-KILE) but here
as well I think we could skip it and just try other KDCs in case of
error.]

2) At this point we assume the service has an SPN of the target
service (with no realm), so it sends an S4U2Proxy request to a KDC in
its own realm including padata PA-PAC-OPTIONS. If service a and b are
in the same realm then the KDC replies with a service ticket and
that's it. If however service-b is in a different realm, the KDC
replies with a referral TGT to the next realm where cname is still of
service-a but the PAC is of the impersonated client, to be used as a
secondary-referral later on. In addition, the PAC includes
PAC_S4U_DELEGATION_INFO field, and it is signed including the realm -
same as in cross-realm S4U2Self requests.

3) Now, service-a needs to identify service-b's realm, and to acquire
referral TGT on behalf of itself to that realm. To do so, service-a
requests a simple ticket to service-b from its own realm, follows
referrals till it gets a service ticket, and voila it knows the target
realm and has a referral TGT for it.
Then, using the secondary-referral as tgt (with the PAC of the
impersonated client), service-a requests a TGT referral to the target
realm (that is, sname is krbtgt) and follows referrals as necessary
(from a KDC in the realm of the secondary-referral). Note that in case
of a direct trust (not transitive) the secondary-referral is already
for the target realm and no actual request is needed (also note that
if a krbtgt request is needed, Windows client adds padata
PA-PAC-OPTIONS so we should add it too, but it looks like Windows KDC
doesn't care about it).

4) The next step, using its own referral as tgt, and the
secondary-referral as secondary ticket, service-a requests a S4U2Proxy
requests from a KDC in target realm including padata PA-PAC-OPTIONS
and the KDC replies with a service ticket where the cname is of the
impersonated client (and so is the PAC).
I'm not sure where it takes the client name from, I suspect it takes
it from the PAC signature by decomposing it, since the name-type gets
lost in the process and it always ends up as KRB5_NT_MS_PRINCIPAL
(which also means this type name supports enterprise-name like, with
an @ sign).

So far, I managed to get MIT client to work against Windows KDC, see POC at:
https://github.com/iboukris/krb5/commits/rbcd

Note, the last commit fixes referrals to work correctly in transitive
trust which I didn't get right at first. I didn't want to squash since
it is more horrible than the first one (as soon as I get something
presentable I'll open a wip PR).

My planning is currently as follows:
- implement the client code properly, I think it might be a good idea
to move some logic from get_creds.c to s4u_creds.c to simplify it
(especially the referral-chasing), but I'm still unclear.
- add basic KDC support, leaving authdata handling to KDB plugin (but
we might need to provide it with more info).
- add tests, possibly using own authdata implementation (similar to
what I experiment with in PR 894).
- manually test windows clients against MIT KDC by plugging it with (a
patched) SambaAD (I'd also try to test trust with Windows KDC).

Thoughts?

Thanks!
_______________________________________________
krbdev mailing list             [hidden email]
https://mailman.mit.edu/mailman/listinfo/krbdev
Reply | Threaded
Open this post in threaded view
|

Re: Implementing RBCD

Greg Hudson
On 3/21/19 6:20 PM, Isaac Boukris wrote:
> [notes: both these requirements are challenging:
> The first would involve full decoding of the PAC, while I think it
> could be a good idea to have it in krb5 library (for other purposes as
> well), I only have a vague idea of what NDR decoding actually means,
> and as far as I remember Greg was not fond of this idea in the past.

I could be okay with this if NDR isn't too hard to decode.

> The second requirement involves an RPC call (as per MS-KILE) but here
> as well I think we could skip it and just try other KDCs in case of
> error.]

I don't think our current sendto_kdc code makes it easy to cycle through
the KDCs on error.  I'd be okay with saying that our side of rbkcd
doesn't work if you have pre-2012 DCs.

> My planning is currently as follows:
> - implement the client code properly, I think it might be a good idea
> to move some logic from get_creds.c to s4u_creds.c to simplify it
> (especially the referral-chasing), but I'm still unclear.
> - add basic KDC support, leaving authdata handling to KDB plugin (but
> we might need to provide it with more info).
> - add tests, possibly using own authdata implementation (similar to
> what I experiment with in PR 894).
> - manually test windows clients against MIT KDC by plugging it with (a
> patched) SambaAD (I'd also try to test trust with Windows KDC).

This seems reasonable.
_______________________________________________
krbdev mailing list             [hidden email]
https://mailman.mit.edu/mailman/listinfo/krbdev
Reply | Threaded
Open this post in threaded view
|

Re: Implementing RBCD

Isaac Boukris
Hi Greg,

On Fri, Mar 22, 2019 at 12:48 AM Greg Hudson <[hidden email]> wrote:
>
> On 3/21/19 6:20 PM, Isaac Boukris wrote:
> > [notes: both these requirements are challenging:
> > The first would involve full decoding of the PAC, while I think it
> > could be a good idea to have it in krb5 library (for other purposes as
> > well), I only have a vague idea of what NDR decoding actually means,
> > and as far as I remember Greg was not fond of this idea in the past.
>
> I could be okay with this if NDR isn't too hard to decode.

I've looked a bit more, doing it properly using IDL would be an
overkill. But manual decoding seems simple enough (per MS-PAC).
Specifically, the UAC does not involve following an internal pointer
and can be reached at a fixed offset. I added a commit that shows how
we could do it for krb5 api (though we could add some sanity checks) .

There is another issue, if we want to check for sensitivity however.
To access the PAC we need to decrypt the ticket for-self, and for that
we need the keytab with long term keys (possibly including other
enc-types).
In GSSAPI, if the ticket for-self was obtained via gss_accept() then
we should be good, but if it is acquired via gss_impersonate(), it
seems that at no point we decrypt the ticket, and I was able in fact
to do constrained-delegation with an initiator TGT only by calling
gss_impersonate() followed by init_sec_context(). To check for
forwardable, the gss-glue code uses 'kdc_rep->enc_part2->flags'
instead of 'kdc_rep->ticket->enc_part2->flags' like the krb5 api does.

I wonder how we can go about it. Perhaps if the ticket is not
forwardable, gss_impersonate() will still issue non-proxy creds as
now, but the caller could still try to call gss_accept() and provide
for new delegated creds, then, using the keytab we check the PAC of
the for-self ticket in cache and create proxy creds if the client is
not sensitive (this is similar to what mod_auth_gssapi is currently
doing afair). Maybe if gss_impersonate() was called using GSS_C_BOTH
creds, then we could try to call gss_accept() internally as a shortcut
and avoid the need of change in the caller application.
_______________________________________________
krbdev mailing list             [hidden email]
https://mailman.mit.edu/mailman/listinfo/krbdev
Reply | Threaded
Open this post in threaded view
|

Re: Implementing RBCD

Greg Hudson
On 3/24/19 2:28 PM, Isaac Boukris wrote:
> There is another issue, if we want to check for sensitivity however.
> To access the PAC we need to decrypt the ticket for-self, and for that
> we need the keytab with long term keys (possibly including other
> enc-types).

Right, this is tricky, because we are used to being able to do
S4U2Self->S4U2Proxy with only a ccache containing the intermediate
service's TGT.  (Interestingly, kvno -P requires a keytab argument,
which it uses to get the client name.  I'm not sure this is necessary,
since the client learns the name during S4U2Self and reports it to the
caller.)

> In GSSAPI, if the ticket for-self was obtained via gss_accept() then
> we should be good, but if it is acquired via gss_impersonate(), it
> seems that at no point we decrypt the ticket, and I was able in fact
> to do constrained-delegation with an initiator TGT only by calling
> gss_impersonate() followed by init_sec_context().

We document that this works (in doc/appdev/gssapi.rst).  t_s4u.c does an
init/accept first; I had always thought this was to exercise more code
paths, but after looking at kvno and at what constrained_delegate() does
with its arguments, I think Luke was concerned about learning the client
name of the decrypted ticket.

> Maybe if gss_impersonate() was called using GSS_C_BOTH
> creds, then we could try to call gss_accept() internally as a shortcut
> and avoid the need of change in the caller application.

I think the simplest answer is to remove the forwardable check and
always generate proxy credentials, letting the KDC decide if the
evidence ticket is valid.  That way unmodified applications will work
with RBKCD.  (Applications have to be prepared for the possibility that
S4U2Proxy fails, since there are permissions on who can
constrained-delegate to what targets.  So adding a case where S4U2Proxy
fails because the evidence ticket isn't valid shouldn't break anything.)

It seems potentially useful to make gss_impersonate() decrypt the ticket
if impersonator_cred_handle is GSS_C_BOTH, if only to set name
attributes based on the ticket's authorization data.  This could be done
with init/accept as you suggest, or with
krb5_server_decrypt_ticket_keytab().  At that point we have enough
information to definitively determine whether the ticket is valid for
S4U2Proxy.

If impersonator_cred_handle is GSS_C_INITIATE, we could still try to
decrypt the ticket using the default keytab, and be prepared for
failure.  I can't decide whether this would violate the principle of
least surprise.

At any rate, if we can't decrypt the ticket internally, I think the
right answer is to assume that the ticket is valid for S4U2Proxy, since
just looking at the forwardable flag isn't sufficient any more.
_______________________________________________
krbdev mailing list             [hidden email]
https://mailman.mit.edu/mailman/listinfo/krbdev
Reply | Threaded
Open this post in threaded view
|

Re: Implementing RBCD

Isaac Boukris
On Tue, Mar 26, 2019 at 5:46 PM Greg Hudson <[hidden email]> wrote:
>
> Right, this is tricky, because we are used to being able to do
> S4U2Self->S4U2Proxy with only a ccache containing the intermediate
> service's TGT.  (Interestingly, kvno -P requires a keytab argument,
> which it uses to get the client name.  I'm not sure this is necessary,
> since the client learns the name during S4U2Self and reports it to the
> caller.)

Technically, kvno also needs the keytab because
krb5_get_credentials_for_proxy() expects the ticket to be already
decrypted in order to check the forwardable flag and (perhaps it was
designed for accept() flow).
If we end up not requiring the forwardable flag, then we can relax
this expectation and subsequently remove the requirement for a keytab
in kvno (though we may still want to know if it was forwardable in
case we want to conditionally check rbcd support in s4u2proxy reply's
encrypted padata as suggested).

> I think the simplest answer is to remove the forwardable check and
> always generate proxy credentials, letting the KDC decide if the
> evidence ticket is valid.  That way unmodified applications will work
> with RBKCD.  (Applications have to be prepared for the possibility that
> S4U2Proxy fails, since there are permissions on who can
> constrained-delegate to what targets.  So adding a case where S4U2Proxy
> fails because the evidence ticket isn't valid shouldn't break anything.)

Yea, in a way it even simplifies caller's expectations. It is also the
simplest to implement, so I'll start with this.

> It seems potentially useful to make gss_impersonate() decrypt the ticket
> if impersonator_cred_handle is GSS_C_BOTH, if only to set name
> attributes based on the ticket's authorization data.  This could be done
> with init/accept as you suggest, or with
> krb5_server_decrypt_ticket_keytab().  At that point we have enough
> information to definitively determine whether the ticket is valid for
> S4U2Proxy.

I see the point in setting name attributes in gss_impersonate(), and
saving the application the need for an extra init/accept, but if we
relax forwardable requirements then I would prefer to defer this,
along with PAC decoding to later, as a separate task.

> If impersonator_cred_handle is GSS_C_INITIATE, we could still try to
> decrypt the ticket using the default keytab, and be prepared for
> failure.  I can't decide whether this would violate the principle of
> least surprise.

I thought that GSS_C_ACCEPT would be needed to access default keytab,
same as we require GSS_C_INITIATE in gss_accept() in order to access
the ccache when we need to create proxy credentials. But I'm not clear
on these concepts.

> At any rate, if we can't decrypt the ticket internally, I think the
> right answer is to assume that the ticket is valid for S4U2Proxy, since
> just looking at the forwardable flag isn't sufficient any more.

Same here. I don't get what security purpose it serves for the
intermediate service to check for client sensitivity, since the KDC
who'd decode the second ticket can and must check it anyway. If
anything I think this check better fits later on, when the proxy
accepts the final ticket (we could add that too).
_______________________________________________
krbdev mailing list             [hidden email]
https://mailman.mit.edu/mailman/listinfo/krbdev
Reply | Threaded
Open this post in threaded view
|

Re: Implementing RBCD

Greg Hudson
On 3/26/19 6:19 PM, Isaac Boukris wrote:
> I see the point in setting name attributes in gss_impersonate(), and
> saving the application the need for an extra init/accept, but if we
> relax forwardable requirements then I would prefer to defer this,
> along with PAC decoding to later, as a separate task.

Not a problem.

> I thought that GSS_C_ACCEPT would be needed to access default keytab,
> same as we require GSS_C_INITIATE in gss_accept() in order to access
> the ccache when we need to create proxy credentials. But I'm not clear
> on these concepts.

In a technical sense, nothing stops us from calling krb5_kt_default() on
our krb5_context and trying it.

But the more I think about doing this the more I don't like it.  If an
application needs behavior which is dependent on decrypting the ticket,
it should identify the keytab to the GSS layer or do its own init/accept.
_______________________________________________
krbdev mailing list             [hidden email]
https://mailman.mit.edu/mailman/listinfo/krbdev
Reply | Threaded
Open this post in threaded view
|

Re: Implementing RBCD

Benjamin Kaduk-2
On Tue, Mar 26, 2019 at 07:41:20PM -0400, Greg Hudson wrote:

> On 3/26/19 6:19 PM, Isaac Boukris wrote:
> > I see the point in setting name attributes in gss_impersonate(), and
> > saving the application the need for an extra init/accept, but if we
> > relax forwardable requirements then I would prefer to defer this,
> > along with PAC decoding to later, as a separate task.
>
> Not a problem.
>
> > I thought that GSS_C_ACCEPT would be needed to access default keytab,
> > same as we require GSS_C_INITIATE in gss_accept() in order to access
> > the ccache when we need to create proxy credentials. But I'm not clear
> > on these concepts.
>
> In a technical sense, nothing stops us from calling krb5_kt_default() on
> our krb5_context and trying it.
>
> But the more I think about doing this the more I don't like it.  If an
> application needs behavior which is dependent on decrypting the ticket,
> it should identify the keytab to the GSS layer or do its own init/accept.

I'm inclined to agree -- as you say in your other message, this rather
violates the principle of least surprise, too.

-Ben
_______________________________________________
krbdev mailing list             [hidden email]
https://mailman.mit.edu/mailman/listinfo/krbdev