This is a simple shim designed to enable smartcard login support for the Microsoft Teams app on MacOS X. When using this shim, you should be able to launch Teams and use your smartcard to authenticate to Teams.
Download the latest package from the
release page, open
Terminal.app, and run /usr/local/bin/cac4teams
from a command-line
window. If everything is working properly, Teams should come up
(the first time you might need to allow it access to some Microsoft
keychain items) and you should be able to use your smartcard to
authenticate to Teams at the appropriate point.
If you have a LOT of certificates in your Keychain (especially expired ones) MacOS X can take a long time to search through the certificate list when making certain Security framework function calls (I have seen cases where it can take a couple of minutes). You can test if this is the case if you try to go to CAC-enabled web sites with Safari; does it hang or take a long time before you get a client certificate selection dialog? Then this is the problem.
You can try removing the expired certificates from your Keychain,
but if you don't want to do that or it doesn't help, you can set
the environment variable CAC4TEAMS_NOISSUERS
to y
when running
cac4teams
. This will cause cac4teams
to remove the issuer list from
the query dictionary and should speed up the call significantly.
Well, SINCE you're asking ... not exactly.
I noticed when trying to use the Teams app that it got rather far in the client certificate negotiation. A window would pop up asking you to select a client certificate to use, but the list of available certificates was always blank. This kind of surprised me, as in my experience if you get that list of certificates the application should just work.
So, fast forward a bit later ... I poked around the Teams app a bit, and I discovered that there was a legitimate bug in the Teams app, so I wondered if I could create a workaround for the bug, would you be able to use a Smartcard? Turns out the answer is "yes"!
Well, it gets kind of technical but since you asked ...
To query Keychain entries (including smartcards) most of the time you
call a function called SecItemCopyMatching()
. You give it a dictionary
containing all of the parameters of your query, and exactly what you
want it to return. So if you want to find out a certificate with a
particular issuer, you might say, "I'm looking for certificates, they
have to have this issuer, and I'd like you to return the certificate
data".
When I dug into it, I found Teams was calling SecItemCopyMatching()
and the query dictionary said it was looking for identities (a certificate
plus private key, which is correct), certificates which matched a given
set of issuers (also correct), but the query dictionary did not include
any information on what to return. So SecItemCopyMatching()
would
always return nothing. Also, the Teams app is signed and enrolled in
the hardened runtime environment (which is good) but it lacks a special
entitlement called com.apple.security.smartcard
which grants
applications access to smartcards.
Well, in digging more into this I found that they are using something
called Electron as a cross-platform
GUI framework for Teams. And Electron contains a large chunk of the
Chromium source code in it, and Chromium on Mac already has support
for smartcards. My guess is that some Microsoft programmer did a
cut & paste and missed one or two items when creating the query
dictionary for SecItemCopyMatching()
. Or maybe the bug is in
Electron somewhere; I felt like I had dug into it enough, tracking down
the problem more wasn't going to be helpful. The bottom line is
that without a return flag set in the query dictionary, nothing would
ever be returned from SecItemCopyMatching()
.
As for the missing com.apple.security.smartcard
entitlement, I suspect
they just didn't know about that one.
What cac4teams
does is it uses something called dyld interposing to
intercept the SecItemCopyMatching()
function call. If it detects that
it has been called with a query for identities AND there are no
results flag set in the dictionary, it will add the appropriate return
flag to the query dictionary and then call the real SecItemCopyMatching()
function. In that case (assuming you have a working smartcard) the
certificate selection dialog will appear with the certificates on your
smartcard and at that point everything works.
Well, no, not exactly.
Assuming your application is signed and notarized, it has to be enrolled in what Apple calls the hardened runtime environment. This means the application has a bunch of security restrictions surrounding it, like you don't get access to the microphone or the camera (or smartcards) unless they are specifically granted by what are called "entitlements". This is good! That means applications that are buggy or compromised don't get access to resources that they shouldn't have access to.
The way that dyld interposing works is you need to set a special
environment variable (DYLD_INSERT_LIBRARIES
) pointing to a library
you wish the dynamic linker to load at startup time. This library
has the ability to intercept any dynamically loaded function.
But applications that are enrolled in the hardened runtime environment do not permit the use of environment variables to modify the behavior of the dyanmic linker (unless specifically permitted by entitlements); I personally haven't ever encountered an application that permits this via entitlements, and the Teams app definitely does not. So it's not something you can do under ordinary circumstances.
So what is happening is cac4teams
is a shell script which does the
following things:
- It copies the entire Teams application into
/tmp
. It extracts the existing entitlements used by Teams and adds two new ones:com.apple.security.smartcard
for smartcard access andcom.apple.security.cs.allow-dyld-environment-variables
to allow the use of dyld environment variables to permit interposing.- It signs the copy of Teams in
/tmp
using an ad-hoc signaturewith the extra entitlements added. - It then runs the copy of Teams in
/tmp
with the specialDYLD_INSERT_LIBRARIES
environment variable pointing to a special shared library which does the appropriate adjustment to theSecItemCopyMatching()
dictionary mentioned previously.
(Sigh. I tried doing the right thing, but it turns out that I could never reliably get that to work. So, the shell script ends up not enrolling the temporary copy of Teams in the hardened runtime environment).
No, no, it's okay. I understand.
The best thing I can tell you is take a look at the source code. It is actually quite small! The shell script source code is here and the shared library shim is here. It really is a small amount of code.
If you feel uncomfortable about running the ad-hoc signed copy of Teams
from /tmp
, I did discover that your identity token is cached for an
unknown amount of time by the application. So you CAN run cac4teams
,
log in with your smartcard, quit the cac4teams
copy of Teams and
then launch the real copy of Teams and it will use your saved identity
token. I only tested that with Navy Flank Speed; how that works on other
instances of Teams is unknown.
I realize it's not possible to know if the pre-built binary package
actually corresponds to that source code unless you disassemble the
library, and most people won't be capable of doing that. The installers
are provided as a service to the community to make distribution easier,
but I encourage anyone who unsure about the installer to build the package
theirself. You'll need XCode or the Command Line Developer Tools
installed to do that. Note that if you wish to build your own installer
package all of the tooling is in the Makefile
(see the product
and
notarize
targets) but you'll need both a Developer ID Application and a
Developer ID Installer identity from Apple in your Keychain to create a
signed and notarized package. See configure --help
for more information
on how to specify those identities.
Yeah, it turns out that if you run Teams from the command line, you get a
whole pile of warnings, even without cac4teams
. You just normally don't
see all of those warnings because they're hidden by the MacOS X Finder.
Well, I did try but I ran into a problem right away, which is that I couldn't figure how to do that.
If you select "Help" in Microsoft Teams, it just pulls up a generic help page in the application that has things like "What to do if you have trouble logging in". There was no place to submit bug reports that I could find. I looked around the Microsoft web site, but there didn't seem to be a way to submit bugs for Teams (there was a "submit comments about Teams, but that didn't seem like a place for bug reports).
I asked my management chain if there was a way to contact Microsoft through official DoD channels, and everyone pretty much said ¯\(ツ)/¯.
I understand that Microsoft takes this approach to cut down on support costs, but it does make submitting bugs difficult.
Well, have you ever TRIED doing that? In my experience when dealing with problems like this and large organizations it is very difficult to get problems like this past bureaucratic intertia and into the hands of the appropriate developers.
Let's take a completely, made-up example, which is in no way related to
any real-world events. Let's say you are trying to get a DoD PKI
certificate issued with an id-pkinit-san
SubjectAlterateName, which
the NPE portal claims it supports. But when you get the signed
certificate back it doesn't work at all. So after a few days of
staring at the output of openssl asn1parse
and reading multiple
RFCs, you find that the ASN.1 explicit tags for the KRB5PrincipalName
field are all 0 where they should be 1, in violation of
RFC 4556. So you
figure this is a very clear bug, and you decide to work the system
the proper way. So you write up some very long and hopefully clear
emails and you start working it up the management chain, starting at your
local security office, and eventually you get punted to someone
at DISA who seems to understand your bug, you start a dialog ... and
you end up getting completely ghosted, with the bug never getting
fixed at all.
It is incredibly demoralizing and depressing to go through all of the effort to document and submit a bug through multiple levels of bureaucracy only to get radio silence. So I decided to focus my energy on creating a solution rather than just complain about the problem.
I would again like the emphasize the above scenario is COMPLETELY hypothetical and absolutely DID NOT happen to me.
Well, if I am being COMPLETELY honest what I am hoping is that eventually this package makes its way to the developers at Microsoft and the bug is quietly fixed and rolled out in a subsequent releae of Teams. I know that sounds farfetched, but stranger things have happened.
I don't quite feel comfortable running this software package, but I'd still like to help somehow. Is there anything I can do?
Yes! If you could try to run this down via the management chain at YOUR organization, that would be incredibly helpful. My impression is that the more people that complain about a problem, the better. Don't make the case that you're asking for a feature request; this is obviously a bugfix.
- Ken Hornstein - kenh@cmf.nrl.navy.mil