-
Notifications
You must be signed in to change notification settings - Fork 78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CT-KIP support #27
Comments
Any plans for progress on this? |
I've got a MITMproxy capture of the whole process. It's not too complex but wrapped in a bunch of fugly XML two layers deep (with SOAP on top) and a "custom" AES-based MAC algorithm for everything. 🤦♂️. For RSA SecurID client v5.0.2.440:
By far, the most annoying part is going to be implementing the boutique |
Add totally untested implementations of the OMAC1-AES ("CMAC") and CT-KIP-PRF-* family of pseudo-random functions per relevant RFCs. It compiles but that's about all I've tested. Broken: * Only supports tomcrypt * Untested!!! * Mallocs in PRF-SHA256 variant to work around missing iterative sha256-hmac. Could add iterative hmac instead and skip malloc. Re: stoken-dev#27
I've written a simple "fake" token activation server in Python+Flask. The official RSA SecurID client is willing to talk to it. Gist with fake server: https://gist.github.com/dlenski/e4a53a17c0786f492fc04c901968681d But I'm stuck on how to do the MAC required to validate this. There seem to be multiple layers of intentional obfuscation in terms of how to generate the shared keying material |
It may be more productive to try to "break" this from the client side, if you have access to a real server where you can generate a stream of new activation codes. The client never has to MAC anything or decrypt anything. The problem is that the 16-byte nonce sent by the client and the 16-byte nonce sent by the server almost certainly get combined in some obfuscated way that only the official RSA software really understands. So a naïve client will be able to deliver its 16-byte nonce to the server, which will then use it to build the final token seed, but figuring out what that final seed value is will prove difficult… |
I don't have direct access to a server. I guess I could try asking my company's IT department for new activation codes repeatedly, but they might deny my requests at some point 😂. Edit: Actually, if I just need to be able to access something like what I posted in the first comment, I may able to try things. |
Bringing the discussion back from my gist because you can't get notifications on Gists — @cemeyer wrote:
Yep, big-endian, modulus only seems plausible.
Unfortunately not. Even with a complete, verbatim exchange, we have no idea what the MAC is over because the RSA private key is needed to decrypt the client nonce.
Agreed. Upon my initial read of the RFC, I thought that reverse-engineering the server would be easier because the client software can be run privately while the server only hands out one-time activation codes to attempt (and I have to go through a grumpy IT department). I think that writing a "working" client should actually be trivial, because the client doesn't actually have to sign anything or use any obfuscated algorithms. I put "working" in quotes because the likely outcome is that the server accepts the client's nonce, and then we have no clear idea what the resulting K_TOKEN is… but at least we then have a somewhat better oracle, in that we know both the client nonce and the server nonce, and can try generating the resulting codes with stoken and using them to log into [whatever the token is used for].
Similar. It'd be awkward for me to ask for more than a "small-single digit" number of activation codes. 😂 |
@cemeyer, here's a client implementation: dlenski/rsa_ct_kip:client.py. Run as
Not really tested, but it should be enough to get this "better oracle", since it will print both server and client nonces (unencrypted), as well as the server "MAC"/PRF output. |
Nice :-). So if someone is brave enough to use this, the client may actually correctly activate a key with the server, but we won't actually know what that key is :P. Although, with the client-side plaintexts+PRF output we can probably figure it out offline. |
@dlenski Hey, so I got a new phone and have a legitimate need to request a new token anyway. What testing can I do with a fresh token that would be valuable? Is the Python client from two comments ago still a useful test? Thanks! Edit: Digging through old email. Some alternate (Android?) form of the URL looks like:
|
Yes, it should be. You'll get all the unencrypted nonces that way. If you can figure out how to combine them to get the actual seed for the token… then we've got ourselves a tool for destupidifying the RSA tokens. 👍 I haven't been able to do any further work on this since I no longer have access to a client that uses CT-KIP. |
You've already got the server side recorded, right? Would it be a useful test to run a dry-run registration against a fake server with real data to capture the exchange (to whatever point it succeeds)? I've seen some documentation of the official RSA client being able to ignore untrusted CAs. Since the real server doesn't know the client has registered, I don't think that will use up the activation code. Thanks! |
Yes, but you can do this part at any time, using the ordinary client and fakeserver.py. What do we learn from it? Unfortunately, not much. We confirm that the official client and fake server can indeed talk to each other, and we learn the value of the unencrypted client and server nonces… but the client doesn't accept the final "MAC" (which is probably not really a MAC… but whatever).
The official client doesn't pin its CAs in any way, as far as I can tell. If Windows trusts the CA, the official client will trust it too.
Correct. You can test against the fake server all you want, and the real server won't know anything about this. Honestly, this whole CT-KIP provisioning thing is so unreliable that no IT department will be surprised if you have to request that they regenerate the activation code for you 5 times over a period of a day or two. :-P Probably the right way to make progress here:
|
💯 Re: suggested steps, I'll see what I can do 😂 😎 Edit: server uses 1024 bit RSA? Is that trivially factorable yet, or just 1-2 years away? 😂 😂 😂 |
FYI, https://community.rsa.com/docs/DOC-85572 confirms the device binding ID you preregister with the admin server is a 24-char hexadecimal string and is "generated" (suggests random, but could be computed or include a checkcode or something).
Not sure how that works with CT-KIP; I don't see it in the fakeserver.py. Re: key-is-computed-based-on-real-server's RSA, probably:
Well, we knew that.
(lol) ...
|
I don't think I can get a MITM server set up in the timeframe I have available. I can certainly at least try the fake client and record some results. If the e.g. Windows SDK runs in wine that might be an interesting enough avenue for me, even if it doesn't work with stoken. |
The RSA docs are so convoluted and perhaps intentionally obfuscated that I don't get much out of that. The part about device binding is kind of interesting though: it seems to be saying that the "device binding" is enforced purely on the local side, by the RSA SecurID client software verifying that the host computer's device-binding-ID matches the one saved in the token. (In other words, a free implementation like
I say "go for it." Best case is that you capture all of the nonces and figure out how the token secret is generated. Worst case is smaller incremental progress. Like I said, I no longer have access to a "real" RSA CT-KIP orac… er, generator… so I can't help. |
Yeah, no worries. One other interesting tidbit is that the CT-KIP negotiation code in the official windows client does not appear to be obfuscated at all. There are direct strings of "ct-kip-prf-aes", "StartService", "application/vnd.otps.ct-kip", "SOAPAction", "EncryptedNonce" in rsatokenbroker.exe1 and sdui_softwaretoken.dll of the official 5.0.2 client that are pointed to directly from the code section. I suspect anyone familiar with basic windows RE techniques could extract the official client algorithm pretty easily (I'm not familiar, unfortunately). |
Oh yeah and this may be helpful to someone working on a MITM server: cemeyer/rsa_ct_kip@21cef1d |
Ok! Successful handshake between naive client and real server. Edit:
We know:
The piece we don't have here is exactly how the server's RSA pubkey is incorporated into the input for the PRF to generate K_TOKEN, but we should have everything we need. |
I got the same result.
… except for a sane definition of the #@*$&U!
Agreed. You also now (hopefully) have a binary oracle that can tell you whether or not you've figured out the right |
Yeah, exactly! |
I found the self-service portal 😂 😂 😂 Looks like they use the same RSA public key for all |
Nice! So you can generate new activation codes at will?
"for all |
So far!
For all 2/2 tokens I've received for my particular LDAP user on the same server. Low sample size but if they were per-activation I'd expect to have seen different keys. But I see a different |
Hey guys, I have reverse engineered the mobile RSA SecurId app and found how the byte a[] = func1(client_nonce, server_nonce, rsa_modulus);
byte b[] = func2(client_nonce, a);
// "a" and "b" are byte arrays with 16 elements
for (int i = 0 ; i < 16 ; i++) {
if (b[i] != mac[i]) {
throw new InvalidMac();
}
} The byte array So now the question is how to derive the stoken seed from this 16 byte shared secret? |
I think we're hoping the stoken seed is just the shared secret. |
I have uploaded the sources: https://github.com/rgerganov/ctkip There is also a |
OK, I upgraded I believe we are pretty close ... |
I fixed the magic string in Any idea how to import the 16 byte secret into stoken? |
Wow, this is impressive work… hooray!
This is a good question. I believe you would have to basically use |
If you have a raw 16-byte
The extra '=' at the start of the base64 string is mandatory (the RSA tools require it). |
Probably I was doing it wrong. :-) The trick seems to be Edit: oh ffs, the string is Computation, not computation. RFC strikes again. Edit2: Oh I see, you are decoding the base64 modulus, not leaving it as-is. I'm still not getting the right MAC in Python but I'm sure I'm doing something wrong. Hm. Edit3: Oh, ok, ct-kip-prf is also documented out of order w.r.t. implementation. The block number is concatenated after the input msg, not before it. Now I get the correct MAC in Python. Stupid RFC. Edit4: https://gist.github.com/cemeyer/7ebafafc616830faf6fec5c9f1abaa9b is the slightly less cryptic i.java class. I swapped the order of the b() methods but otherwise left order alone. I changed some method names inside that file to make it clearer what they did. |
Working Python implementation: https://gist.github.com/cemeyer/3293e4fcb3013c4ee2d1b6005e0561bf |
This is awesome 😍 Do I have this right?
So can it all be put together… can you make the the Python client verify the MAC and create a working token? |
In other words, there would be no chance of implementing RSA's convoluted "open standard" in a way that interoperates with their software without reverse engineering… because they got the order wrong in Section 3.5 of RFC4758 🤦♂️
What RSA SecurID software apparently actually does.. to compute
|
Yeah. They also got §3.8.6 wrong:
And §D.2.2:
It's hard to see this as unintentional, IMO. Edit: |
I'm not sure if it's malice or incompetence… but not surprised that it's full of mistakes 😡. My first take when we were discussing the RFC is that it was incredibly sloppily written, and that it appeared to be describing the operation of an existing program, rather than actually a genuine attempt to describe a new standard.
|
I made a gist with Provide it with the seed (32 hex digits), serial number, and expiration date, and it'll convert it to CTF format:
You can use
|
Add totally untested implementations of the OMAC1-AES ("CMAC") and CT-KIP-PRF-AES pseudo-random function per relevant RFCs (but see also: mistakes in the RFC mentioned in stoken-dev#27). It compiles but that's about all I've tested. Broken: * Only supports tomcrypt * Untested!!! Re: stoken-dev#27
I've fixed the C implementation of ct_kip_prf_aes in my stoken fork on GH to match the implementation as described earlier on this thread and just went ahead and removed the -SHA variant. I still haven't tested it at all yet, and obviously it's just a small piece of this. I have to run for now but might be able to play with this later. Thanks everybody. |
The latest version of dlenski/rsa_ct_kip incorporates the Arguments for
This
And correspondingly from the server log (filtering out the junk):
However, I still can't get the real RSA client to accept the MAC sent by If any other brave soul would like to try running |
@dlenski Hm, the I can see the whole exchange of messages but at the end the mobile app says |
@rgerganov, this is the same problem that I'm having. How can we be sure that it's the correct MAC, since nothing else seems to be wrong? If you use the |
Yes. I get the shared secret ( |
Excellent! Sounds like we have all of the pieces! |
Eureka! It's all working now over at https://github.com/dlenski/rsa_ct_kip. The real RSA windows client will now talk to the fake server (the real client is stupidly fiddly about the exact formatting of the XML messages… I don't think it really parses them) and I confirm that it produces the expected token codes. The Python client can talk to the real RSA server, provision a token, and explain how to use the output with
|
That is AWESOME! Any chance to get the client bits ported into stoken so that it can fetch from ctkip? |
This is a job for someone who has an appetite to (re)write a bunch of extraordinarily ugly XML-and-web-services code in C. 😂 For now… there's a functional solution in Python for anyone who just needs to provision a working token. |
YES! I confirm it's also working with the mobile version. Great work :) |
I was able to resync my token successfully but I'm not sure what my pin is and locked myself out 😂 . Not stoken's fault, seems to be working (or resync would have failed). Edit: can confirm, |
@cemeyer, might be a good idea for you to update your original post with a link to the Python tool if you think it's a worthy solution. It can now be auto-installed with |
Yep, good idea. I'll do so. I'd like to see native support, and this ticket can track that, but for now we have a usable workaround. Edit: RFC errata submitted. |
Good on you for submitting this. 👌 |
URLs look like
com.rsa.securid://ctkip?url=https://XXX.com:443/ctkip/services/CtkipService
and come with a 12 decimal digit activation code.The protocol is documented in this RFC: https://tools.ietf.org/html/rfc4758
Edit 2019-01-27: Some exciting news! While not yet integrated into stoken, users with these activation tokens can derive the shared secret and import it into
stoken
with Dan Lenski's excellent https://github.com/dlenski/rsa_ct_kip client.py!The text was updated successfully, but these errors were encountered: