Giter VIP home page Giter VIP logo

Comments (53)

dlenski avatar dlenski commented on August 30, 2024 6

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 stoken:

$ ./client.py https://server.company.com:443/ctkip/services/CtkipService ACTIVATION_CODE -s template.xml
Sending ClientHello request to server...
Received ServerHello response with server nonce (R_S = 28198dbe2c18a00335179cc5bb4eff3a) and 1024-bit RSA public key
Generated client nonce (R_C = 12bec1a6f4d09470986b485561c4d2b5)
Sending ServerFinished request to server, with encrypted client nonce...
MAC verified (0f103bc63a8819ffdbee657d042144f6)
Received ServerFinished response with token information:
  Key ID: 838999658504
  Token ID: 838999658504
  Token User:
  Expiration date: 2020-01-23T00:00:00+00:00
  OTP mode: 8 Decimal, every 60 seconds
  Token seed: 30ade1be20b3867d967bd2927c8eb0ca
Saved template to template.xml. Convert to XML format (.sdtid) with:
  stoken export --random --sdtid --template=template.xml > 838999658504.sdtid

from stoken.

cernekee avatar cernekee commented on August 30, 2024 4

If you have a raw 16-byte dec_seed, you can base64-encode it and write it into an XML template file, then ask stoken to construct a new token file from it:

$ cat tpl.xml
<?xml version="1.0"?>
<TKNBatch>
  <TKN>
    <SN>000000000001</SN>
    <Death>2020/01/01</Death>
    <Seed>=AAECAwQFBgcICQoLDA0ODw==</Seed>
  </TKN>
</TKNBatch>
$ echo "AAECAwQFBgcICQoLDA0ODw==" | base64 -d | hexdump -C 
00000000  00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f  |................|
00000010
$ stoken export --random --sdtid --template tpl.xml > token.xml
$ stoken show --file=token.xml --seed
Serial number           : 000000000001
Decrypted seed          : 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 
Encrypted seed          : b7 ff a6 f9 9f 57 f0 16 90 15 b6 25 4a b6 24 5d 
Encrypted w/password    : no
Encrypted w/devid       : no
Expiration date         : 2020/01/02
Key length              : 128
Tokencode digits        : 8
PIN mode                : 3
Seconds per tokencode   : 60
App-derived             : no
Feature bit 4           : no
Time-derived            : yes
Feature bit 6           : no

The extra '=' at the start of the base64 string is mandatory (the RSA tools require it).

from stoken.

dlenski avatar dlenski commented on August 30, 2024 3

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:

  1. the client says hello and sends the 12-digit activation code,
  2. the server sends back its RSA public key and a 16-byte nonce (MAC'ed),
  3. the client sends back a 16-byte nonce encrypted with that RSA public key via PKCS#1 OAEP
  4. The server replies with some details about the OTP output format, and confirms that it has grokked the whole conversation by sending a MAC of (a) the client and server nonces, which are straightforward, combined with (b) some K_AUTH mystery meat key that the RFC is very vague about.

By far, the most annoying part is going to be implementing the boutique CT-KIP-PRF-AES MAC algorithm (from Appendix D.2.2).

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024 3

I found the self-service portal 😂 😂 😂

Looks like they use the same RSA public key for all k. That might be useful.

from stoken.

rgerganov avatar rgerganov commented on August 30, 2024 3

Hey guys,

I have reverse engineered the mobile RSA SecurId app and found how the Mac from ServerFinished is used:

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 a is stored and byte array b is discarded, so I believe that a is the shared secret. The implementation of func1 and func2 is just byte twiddling, no 3rd party algorithms is used. Will upload their source tomorrow.

So now the question is how to derive the stoken seed from this 16 byte shared secret?

from stoken.

rgerganov avatar rgerganov commented on August 30, 2024 3

@cernekee Thank you so much, it worked! The only catch was that I had to set the serial number to match the TokenId from the ServerFinished message. Now I can login with tokens generated by stoken 😄 🎉

@dlenski @cemeyer I will start looking into the python stuff now

from stoken.

rgerganov avatar rgerganov commented on August 30, 2024 3

If you use the client.py with a real server, does it actually work to get you the correct K_TOKEN?

Yes. I get the shared secret (K_TOKEN) and TokenID with client.py, then create token.xml, import into stoken and successfully generate valid tokens against a real server.

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024 3

Excellent! Sounds like we have all of the pieces!

from stoken.

dlenski avatar dlenski commented on August 30, 2024 2

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 ☹️

from stoken.

dlenski avatar dlenski commented on August 30, 2024 2

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…

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024 2

@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:

http://127.0.0.1/securid/ctkip?scheme=https&url=foo.baz.com:443/ctkip/services/CtkipService

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024 2

Ok! Successful handshake between naive client and real server. Edit: KeyID and TokenID are identical and both match the "serial number" I was told ahead of time. Expiration is 2.75 years in the future, which seems a bit odd. So:

      K_TOKEN = CT-KIP-PRF (R_C, "Key generation" || k || R_S, <arbitray output length>)

      MAC = CT-KIP-PRF (K_AUTH, "MAC 2 computation" || R_C, len(R_C))

We know:

  • R_C (client nonce)
  • k (server RSA pubkey)
  • R_S (server nonce)
  • MAC

For the purposes of this document, the secret key k shall be 16 octets long

  • K_TOKEN len

If no authentication key is present in the token, and no K_TOKEN existed before the CT-KIP run, K_AUTH shall be the newly generated K_TOKEN.

  • K_AUTH = K_TOKEN

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.

from stoken.

rgerganov avatar rgerganov commented on August 30, 2024 2

I fixed the magic string in com.rsa.ctkip.b.a.c and now it works 🎉

Any idea how to import the 16 byte secret into stoken?

from stoken.

rgerganov avatar rgerganov commented on August 30, 2024 2

Eureka!

It's all working now over at https://github.com/dlenski/rsa_ct_kip.

YES! I confirm it's also working with the mobile version. Great work :)

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024 2

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.

from stoken.

dlenski avatar dlenski commented on August 30, 2024 2

Edit: RFC errata submitted.

Good on you for submitting this. 👌

from stoken.

dlenski avatar dlenski commented on August 30, 2024 1

Bringing the discussion back from my gist because you can't get notifications on Gists ☹️.

@cemeyer wrote:

I hope the RSA pubkey representation fed into the PRF isn't .der and especially PEM, but I don't really know. Could try the DER order (modulus n, followed by exponent e), but I don't know how long of an encoding e would have. Then again PKCS 1 specifies e=65537, so maybe just try the (big-endian, i.e., I2OSP) modulus (which should have an exact encoding width).

Yep, big-endian, modulus only seems plausible.

Yeah. I've hunted around a little bit and I'm having trouble finding any other CT-KIP-PRF implementation to compare against (not the same as known test vectors, but better than nothing). I wonder if the (single) MAC in https://tools.ietf.org/html/draft-nystrom-ct-kip-two-pass-01#page-30 is a valid test vector. But I have not investigated what it is supposed to be over, or if the key is even supplied.

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.

Yeah, we have a very low information oracle on whether we got it right. Hmm. Maybe reversing the client would be more straightforward.

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].

I guess I could try asking my company's IT department for new activation codes repeatedly…

Similar. It'd be awkward for me to ask for more than a "small-single digit" number of activation codes. 😂

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024 1

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.

from stoken.

dlenski avatar dlenski commented on August 30, 2024 1

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)?

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).

I've seen some documentation of the official RSA client being able to ignore untrusted CAs.

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.

Since the real server doesn't know the client has registered, I don't think that will use up the activation code.

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:

  1. Write a MITM version which acts as both a client and a server. It should pretend to be a real server to the client, and pretend to be a real client to the server.
  2. This MITM script can capture (a) the server nonce, which is always unencrypted anyway and (b) the unencrypted client nonce passed to the server and (c) the "MAC" returned by the real server to the client.
  3. But still, the likely outcome is that the "real" client rejects the MAC returned by the real server… because it's computed based on the real server's RSA key, rather than the MITM's RSA key. (If not, then this is a hilarious fail by RSA.)

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024 1

Oh yeah and this may be helpful to someone working on a MITM server: cemeyer/rsa_ct_kip@21cef1d

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024 1

Nice! So you can generate new activation codes at will?

So far!

"for all k"… globally, anywhere? Or for all k handed out by the same server?

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 k than in your pasted example earlier, so probably not global.

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024 1

func1 is some function of "Key generation", R_C (client_nonce), R_S (server nonce), and k (apparently just the RSA modulus, nice!). func2 is probably some function of "MAC 2 computation" and R_C. I'm curious about what particular function of R_C, R_S, and k func1 is — I tried modulus-only, but without any padding, and it didn't seem to match.

I think we're hoping the stoken seed is just the shared secret.

from stoken.

rgerganov avatar rgerganov commented on August 30, 2024 1

I have uploaded the sources: https://github.com/rgerganov/ctkip

There is also a Main class which invokes the decompiled RSA source with pre-recorded R_C, R_S and RSA modulus. However I get ArrayIndexOutOfBoundsException. I managed to fix some trivial compile errors in the decompiled source (see git history) but this is something different. Still looking.

from stoken.

rgerganov avatar rgerganov commented on August 30, 2024 1

OK, I upgraded jadx to its latest version and now the decompiled version of com.rsa.ctkip.b.a.a works. However, the produced MAC doesn't match the recorded value from cemeyer/rsa_ct_kip:client.py. Btw, I had to make some slight changes to this client to make it work and posted a PR. Am I doing something wrong on the client part?

I believe we are pretty close ...

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024 1

Working Python implementation: https://gist.github.com/cemeyer/3293e4fcb3013c4ee2d1b6005e0561bf

from stoken.

dlenski avatar dlenski commented on August 30, 2024 1

This is awesome 😍

Do I have this right?

  1. First, I wrote a crude client dlenski:rsa_ct_kip/client.py that would mimic the RSA client and talk to the provisioning server (cleaned-up version: cemeyer:rsa_ct_kip/client.py).
  2. @rgerganov and @cemeyer have a working ct_kip_prf_aes in Python which will allow the client to derive the token secret and the (MAC matching the ServerFinished message).
  3. @cernekee explained how to load the the token into stoken from the (seed, expiration date, token ID) without wholly re-implementing the token encoding algorithm.

So can it all be put together… can you make the the Python client verify the MAC and create a working token?

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024 1

Yeah. They also got §3.8.6 wrong:

--- MAC = CT-KIP-PRF (K_AUTH, "MAC 2 computation" || R_C, dsLen)
+++ MAC = CT-KIP-PRF (K_AUTH, "MAC 2 Computation" || R_C, dsLen)

And §D.2.2:

--- F (k, s, i) = OMAC1-AES (k, INT (i) || s)
+++ F (k, s, i) = OMAC1-AES (k, s || INT (i))

It's hard to see this as unintentional, IMO.

Edit: stoken export doesn't really work unless you already have a token, I think. Looking at the code I don't see how it can work without one.

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024 1

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.

from stoken.

rgerganov avatar rgerganov commented on August 30, 2024 1

@dlenski Hm, the fakeserver.py doesn't work for me either (I am using the RSA SecurId for Android) although it generates the correct MAC. By the way you can run the whole thing over plain HTTP by using the following URL format: http://127.0.0.1/securid/ctkip?scheme=http&url=<fake_server_host>:<fake_server_port>

I can see the whole exchange of messages but at the end the mobile app says Token import failed.

from stoken.

dlenski avatar dlenski commented on August 30, 2024 1

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.

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024 1

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, stoken works with the CT-KIP shared secret as seed after PIN reset.

from stoken.

pfps avatar pfps commented on August 30, 2024

Any plans for progress on this?

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024

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.

from stoken.

dlenski avatar dlenski commented on August 30, 2024

@cemeyer, here's a client implementation: dlenski/rsa_ct_kip:client.py.

Run as python3 client.py --verbose URL ACTIVATION_CODE.

… 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].

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.

from stoken.

dlenski avatar dlenski commented on August 30, 2024

Is the Python client from two comments ago still a useful test?

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.

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024

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!

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024

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

💯

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? 😂 😂 😂

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024

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).

When a user attempts to import a token bound to a device, the RSA SecurID SDK gets
the binding ID from the token and checks whether the data matches the 24-character
hexadecimal string returned by getBindingId.

Not sure how that works with CT-KIP; I don't see it in the fakeserver.py. TokenID is probably the token serial number, which is different.

Re: key-is-computed-based-on-real-server's RSA, probably:

the CT-KIP process is engineered to prevent the potential interception of the token’s seed.

... use a four-pass CT-KIP protocol to exchange information that is used to dynamically generate a unique shared seed. Information critical to seed generation is encrypted during transmission using a public-private key pair. The generated token seed value is never transmitted across the network.

Well, we knew that.

Your mobile app would then call the importTokenFromCtkip:ctkipAuthCode:validateCert:delegate: method to pass in the underlying CT-KIP data.

If the SSL certificate that you use to secure your CT-KIP connections does not use SHA-256 or later, then you must replace it. The default RSA Authentication Manager SSL console certificates do not meet the ATS requirement.

(lol)

...

Import Procedure

call importTokenFromCtkip:ctkipAuthCode:validateCert:delegate: ... Pass in the URL ... to ctkipUrl, and pass in the activation code ... ctkipAuthCode.

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024

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.

from stoken.

dlenski avatar dlenski commented on August 30, 2024

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 stoken can just ignore it. 😂)

I can certainly at least try the fake client and record some results.

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. ☹️

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024

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).

from stoken.

dlenski avatar dlenski commented on August 30, 2024

Edit: KeyID and TokenID are identical and both match the "serial number" I was told ahead of time.

I got the same result.

We know: <the four explicit parameters that are supposed to go into the construction of K_TOKEN>

… except for a sane definition of the #@*$&U! CT-KIP-PRF, including that important implicit parameter: "exactly how the server's RSA pubkey is incorporated into the input for the PRF."

… but we should have everything we need.

Agreed. You also now (hopefully) have a binary oracle that can tell you whether or not you've figured out the right K_TOKEN (hopefully = seed value as shown by stoken, without further obfuscatory layers).

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024

… except for a sane definition of the #@*$&U! CT-KIP-PRF, including that important implicit parameter: "exactly how the server's RSA pubkey is incorporated into the input for the PRF."

You also now (hopefully) have a binary oracle that can tell you whether or not you've figured out the right K_TOKEN

Yeah, exactly!

from stoken.

dlenski avatar dlenski commented on August 30, 2024

Nice! So you can generate new activation codes at will?

Looks like they use the same RSA public key for all k. That might be useful.

"for all k"… globally, anywhere? Or for all k handed out by the same server?

from stoken.

dlenski avatar dlenski commented on August 30, 2024

I fixed the magic string in com.rsa.ctkip.b.a.c and now it works tada

Wow, this is impressive work… hooray!

Any idea how to import the 16 byte secret into stoken?

This is a good question. I believe you would have to basically use securid_encode_token to convert it into a form that stoken will accept… seems kind of silly to jump through this hoop but I don't know a better way to do it :(

from stoken.

cemeyer avatar cemeyer commented on August 30, 2024

However, the produced MAC doesn't match the recorded value from cemeyer/rsa_ct_kip:client.py. Btw, I had to make some slight changes to this client to make it work and posted a PR. Am I doing something wrong on the client part?

Probably I was doing it wrong. :-)

The trick seems to be K_TOKEN = ct-kip-prf-aes(R_C, k || "Key generation" || R_S) rather than "Key generation" || k || R_S as the RFC claims 😱 . Also k = base64( long_to_bytes(pubk.n) ). I'm still missing something because I'm not getting the expected MAC, though. I've cleaned up the a.java class somewhat for clarity and can post it somewhere.

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.

from stoken.

dlenski avatar dlenski commented on August 30, 2024

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.

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 🤦‍♂️

K_TOKEN computation described in Section 3.5:

--- K_TOKEN = CT-KIP-PRF (R_C, "Key generation" || k || R_S, dsLen)

What RSA SecurID software apparently actually does.. to compute K_TOKEN:

+++ K_TOKEN = CT-KIP-PRF (R_C, k || "Key generation" || R_S, dsLen)

from stoken.

dlenski avatar dlenski commented on August 30, 2024

It's hard to see this as unintentional, IMO.

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.

stoken export doesn't really work unless you already have a token, I think. Looking at the code I don't see how it can work without one.

  1. It does work without .stokenrc file if you use the --random flag. At least v0.9 does.
  2. You have to do an export to the long-winded XML format (--sdtid) in order for the parameters in the --template to be incorporated. If you export to the default CTF ("compressed token format"), the values in the --template will be ignored… it'll just give you a random token.
  3. It would be nice to have a way to import into stoken using "just" the decrypted seed and other values shown in stoken show, but that would basically mean reimplementing the fiddly bits of v2_encode_token which I mentioned above.
$ stoken show --seed=XXXXXXXXXXXXX
Serial number           : XXXXXXXXXXX
Decrypted seed          : 16 bytes in hex
Encrypted seed          : 16 bytes in hex
Encrypted w/password    : no
Encrypted w/devid       : no
Expiration date         : YYYY/MM/DD
Key length              : 128
Tokencode digits        : 8
PIN mode                : 2
Seconds per tokencode   : 60
App-derived             : no
Feature bit 4           : no
Time-derived            : yes
Feature bit 6           : no

from stoken.

dlenski avatar dlenski commented on August 30, 2024

I made a gist with make_RSA_token.sh to illustrate this.

Provide it with the seed (32 hex digits), serial number, and expiration date, and it'll convert it to CTF format:

# Run like this
$ SEED=000102030405060708090a0b0c0d0e0f SN=12345678901 EXPIRATION=2030/12/31 \
>    ./make_RSA_token.sh 
Serial Number: 12345678901
Expiration (YYYY/MM/DD): 2030/12/31
Seed (hex): 000102030405060708090a0b0c0d0e0f
Seed (base64): AAECAwQFBgcICQoLDA0ODw==
Compressed token format: 201234567890173042071776752216050450744647071253341607664173154164001302716774734

You can use stoken show that this token has the intended seed value:

$ stoken show --seed --token=201234567890173042071776752216050450744647071253341607664173154164001302716774734
...
Decrypted seed          : 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 
...

from stoken.

dlenski avatar dlenski commented on August 30, 2024

The latest version of dlenski/rsa_ct_kip incorporates the ct_kip_prf_aes from @rgerganov and @cemeyer.

Arguments for client.py:

$ ./client.py --help
usage: client.py [-h] [-v] [-k] url activation_code

positional arguments:
  url
  activation_code

optional arguments:
  -h, --help       show this help message and exit
  -v, --verbose
  -k, --no-verify  Don't verify server TLS cert

This client.py can talk to fakeserver.py, do the full exchange, get the K_TOKEN and verify the MAC:

$ ./client.py -k https://localhost:4443 foobar

Got server nonce and RSA pubkey:
b'ad10736d27ed143064b2a506c6d67a7e'
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDiiEzL+4HtZrSfbLXYs71Fe0r/
bEz4mMOdpubxyNgGjjx7NJj02dXkHqkPVnUHLR6h3ZuU6OfmU/PpBEWK9lUVrs6B
AA2psVA7BvrZ+JbZC0YcIJ7kt3bsAkYiv37zxhfVKQZr3EoH55ewWfTZ8p5y09y2
qDwc7WzgxGQBaRiecQIDAQAB
-----END PUBLIC KEY-----

Generated client nonce:
	plaintext: b'd64949a1cb1b0bf6ed4d741be965bcc6'
	encrypted: b'dcf16b7ff866c760b6d3387d8d45c02b2adb4373eb9f7ecc7453e26d280452fd0af3b7e5b271c531f3bbba37d6476fbc6c1ef67240937d60cc08e52402e6a4cbf064b4dabf644929ef645929895a4d845e8912b4cf68dcfab8a3f3616ce0074c68fdc46d0548ff8b03bb7277b40fddb917420ab2b03c545f8cec0c6ebcc51bf9'

Got key ID, token ID, key expiration date, and MAC:
KeyID: b'247463689455'
TokenID: b'247463689455'
Expiration: 2020-01-22T00:00:00+00:00
MAC verified. K_TOKEN is: b'f42c449973d94de24fd204b3aaecc51a'

And correspondingly from the server log (filtering out the junk):

ENcrypted ClientNonce: b'dcf16b7ff866c760b6d3387d8d45c02b2adb4373eb9f7ecc7453e26d280452fd0af3b7e5b271c531f3bbba37d6476fbc6c1ef67240937d60cc08e52402e6a4cbf064b4dabf644929ef645929895a4d845e8912b4cf68dcfab8a3f3616ce0074c68fdc46d0548ff8b03bb7277b40fddb917420ab2b03c545f8cec0c6ebcc51bf9'
DEcrypted ClientNonce: b'd64949a1cb1b0bf6ed4d741be965bcc6'
K_TOKEN (hex):  b'f42c449973d94de24fd204b3aaecc51a'
MAC (hex):  b'b57de579c34292aac7e905ede73fa286'

However, I still can't get the real RSA client to accept the MAC sent by fakeserver.py, even after trying to slavishly replicate the exact whitespace idiosyncracies of the real server. ☹️

If any other brave soul would like to try running client.py against a real server, and using make_RSA_token.sh to convert the raw K_TOKEN into something stoken can use… and then finding out if it actually generates the correct codes… that'd be swell. Only do this if you can easily request new activation codes 😀

from stoken.

dlenski avatar dlenski commented on August 30, 2024

Hm, the fakeserver.py doesn't work for me either (I am using the RSA SecurId for Android) although it generates the correct MAC. … I can see the whole exchange of messages but at the end the mobile app says Token import failed.

@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 client.py with a real server, does it actually work to get you the correct K_TOKEN?

from stoken.

jcpunk avatar jcpunk commented on August 30, 2024

That is AWESOME!

Any chance to get the client bits ported into stoken so that it can fetch from ctkip?

from stoken.

dlenski avatar dlenski commented on August 30, 2024

@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 pip3, and I hope it'll Just Work™ for most people.

from stoken.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.