Yubico Forum https://forum.yubico.com/ |
|
[Solved] U2F Signing algorithm with .NET lib https://forum.yubico.com/viewtopic.php?f=33&t=1778 |
Page 1 of 2 |
Author: | bbartlett [ Mon Mar 09, 2015 8:35 pm ] |
Post subject: | [Solved] U2F Signing algorithm with .NET lib |
A couple of sanity check questions regarding implementation of U2F by a replying party. (1) I have decoded the attestation certificate returned during registration of a Yubikey NEO and found sha256RSA listed as the SignatureAlgorithm. I was expecting ECDSA not RSA. Please confirm which digital signature algorithm is implemented on the Yubikey. (2) A correct implementation of U2F by a relying party to authenticate users would require the following, (a) persistence of the key handle, public key and attestation certificate created during registration (from window.u2f.register), (b) no error code returned during authentication (from window.u2f.sign), and (c) verify the digital signature returned from the Yubikey sign operation using a crypto library such as Bouncy Castle. Step (2)(c) is where I am having difficulty. I cannot get _signer.VerifySignature(signature) to return expected boolean results. It should return true if the inputs (public key, bytes to sign, signer algorithm, signature from Yubikey) are correct, otherwise false. I am using the .NET library published on your developer pages. Thank you. |
Author: | henrik [ Tue Mar 10, 2015 10:50 am ] |
Post subject: | Re: U2F Signing algorithm |
bbartlett wrote: (1) I have decoded the attestation certificate returned during registration of a Yubikey NEO and found sha256RSA listed as the SignatureAlgorithm. I was expecting ECDSA not RSA. Please confirm which digital signature algorithm is implemented on the Yubikey. All U2F attestation certificates are ECDSA (in accordance with the spec), but they are signed by our CA, which is RSA. |
Author: | henrik [ Tue Mar 10, 2015 11:17 am ] |
Post subject: | Re: U2F Signing algorithm |
bbartlett wrote: I am using the .NET library published on your developer pages. Just to be clear: We are linking to the .NET library from developers.yubico.com. Yubico is not the publisher of that library. bbartlett wrote: (2) A correct implementation of U2F by a relying party to authenticate users would require the following, (a) persistence of the key handle, public key and attestation certificate created during registration (from window.u2f.register) They key handle and the public key has to be persisted, but usually a U2F library will collect this data in an object (called DeviceRegistration or similar). As a user of a library, you should only have to persist this object. See code example here. The attestation certificate can, but does not have to, be persisted. bbartlett wrote: (b) no error code returned during authentication (from window.u2f.sign) Well... If an error code is returned by the browser, there will be no signature and thus the next step will fail. bbartlett wrote: (c) verify the digital signature returned from the Yubikey sign operation using a crypto library such as Bouncy Castle. Once again, this should be handled by the U2F library. You should not have to deal with crypto libraries yourself. The only thing you should have to do is something like this: Code: u2f_lib.finish_authentication(challenge, device_response, registered_devices) This will code will throw an exception if the signature was invalid. I'm not familiar with the .NET library, but it seems like this is the way the demo server of that libarary does it: Code: memberShipService.AuthenticateUser(model.UserName.Trim(), model.DeviceResponse.Trim()); (this line returns a boolean instead of throwing an exception) |
Author: | bbartlett [ Tue Mar 10, 2015 6:12 pm ] |
Post subject: | Re: U2F Signing algorithm with .NET lib |
Thank you for the prompt and thorough reply. I understand the .NET code is not Yubico code. I have been working directly with the .NET developer for several weeks to troubleshoot the calls into the Bouncy Castle library to verify the signature returned from the Yubikey. The .NET code has been ported from your Java solution. We have tried two methods to load the public key, directly and from the attestation certificate. Both attempts fail to produce expected results. We just wanted to make sure that we are using the correct algorithm selection in Bouncy Castle. There is a bug somewhere in the .NET implementation and we are trying to determine if is the signing algorithm, the public key, the bytes to be signed, or the signature. The signing algorithm is defined here. private readonly ISigner _signer = SignerUtilities.GetSigner("SHA-256withECDSA"); The signature comes right out of Yubikey device response JSON object. The public key is retrieved from the device registration information, and decoded from the byte array encodedPublicKey[]. The DecodePublicKey method is not throwing errors, so we are confident that the public key is loading properly. private readonly DerObjectIdentifier _curve = SecObjectIdentifiers.SecP256r1; X9ECParameters curve = SecNamedCurves.GetByOid(_curve); ECPoint point = curve.Curve.DecodePoint(encodedPublicKey); ECDomainParameters ecP = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H); return new ECPublicKeyParameters(point, ecP); The bytes to be signed are assembled as follows. We have read the FIDO specification and believe this is correct. 32 bytes the hash of the appid. 1 byte indicating UserPresence 0x01 (binary 1). 4 bytes indicating the counter in big endian format such as 0x0009 (binary 9). 32 bytes the hash of the clientData. This results in 69 bytes. Since this pattern has to match exactly what takes place on the Yubikey to generate the signature, it is most likely where the problem occurs. byte[] signedBytes = PackBytesToSign( U2F.Crypto.Hash(Encoding.ASCII.GetBytes(appId)), UserPresence, Counter, U2F.Crypto.Hash(Encoding.ASCII.GetBytes(clientData))); Let me describe what I perceive the expected results to be. If the 4 values (algorithm, public key, bytes to sign, signature) are correctly assembled, the verification should be true. Changing 1 or more bytes in the signature byte array should return false. We have not been able to achieve these results. If we load the public key directly, the result is always false. If we load the load the X509 certificate, the result is always true. The fact that the results change depending on how we load the public key with the other 3 values unchanged, indicates that the public key is not being loaded correctly in one of the two methods, but we can't determine which one is incorrect. In the case where the signature verifies true, when we reverse the entire signature array, the result is still true. That is clearly not valid. I know it is not Yubico's responsibility to fix this, but I am hoping there is another .NET programmer who I might get in contact with through the forum. |
Author: | henrik [ Wed Mar 11, 2015 8:27 am ] |
Post subject: | Re: U2F Signing algorithm with .NET lib |
Ok, now I understand the background A few thoughts:
Code: signedBytes: TJp1zKcX77aMhW8muoJQc39E5pgNMxk8lPoLjpzXYYwBAAAAAepBdB9rg8Ena3vY5hyD09-N3vva-7yGE8LvydFuPTkM signature: MEUCIQCHR7QtUrcPfPY3UEF_IxmxECKc2Ody8b-M3I1LAahuTgIgWz0Oc_3SovRj1kLflA2wjvox3pb1n1UuhcPD1YDH1FU= publicKey: BAARhpOKSZZ4RBs_uPUq79rttBCjJMPsmK9Guquc2_ugfZPz60TBqKRLl-2FSYKN-HEEGf7AfMtYGeaMWIDO3jA= publicKey decoded: EC Public Key X: 1186938a499678441b3fb8f52aefdaedb410a324c3ec98af46baab9cdbfba0 Y: 7d93f3eb44c1a8a44b97ed8549828df8710419fec07ccb5819e68c5880cede30 This is how this data was used: Code: System.out.println("signedBytes: " + BaseEncoding.base64Url().encode(signedBytes));
System.out.println("signature: " + BaseEncoding.base64Url().encode(signature)); System.out.println("publicKey: " + BaseEncoding.base64Url().encode(publicKey)); System.out.println("publicKey decoded: " + crypto.decodePublicKey(publicKey)); crypto.checkSignature( crypto.decodePublicKey(publicKey), signedBytes, signature ); |
Author: | bbartlett [ Wed Mar 11, 2015 11:17 pm ] |
Post subject: | Re: U2F Signing algorithm with .NET lib |
Yes, these values return a true result and if I reverse the signature it returns a false result (Check2 is true and Check3 is false). I also checked the public key x and y values and they matched your values. This indicates the Bouncy Castle code is working properly. We must marshaling the values incorrectly in our code. I also noticed that the signedBytes[33-36] stored the counter in big endian as we expected. Thank you. Signature = Utils.Base64StringToByteArray("MEUCIQCHR7QtUrcPfPY3UEF_IxmxECKc2Ody8b-M3I1LAahuTgIgWz0Oc_3SovRj1kLflA2wjvox3pb1n1UuhcPD1YDH1FU="); publicKey = Utils.Base64StringToByteArray("BAARhpOKSZZ4RBs_uPUq79rttBCjJMPsmK9Guquc2_ugfZPz60TBqKRLl-2FSYKN-HEEGf7AfMtYGeaMWIDO3jA="); signedBytes = Utils.Base64StringToByteArray("TJp1zKcX77aMhW8muoJQc39E5pgNMxk8lPoLjpzXYYwBAAAAAepBdB9rg8Ena3vY5hyD09-N3vva-7yGE8LvydFuPTkM"); bool Check2 = U2F.Crypto.CheckSignature( U2F.Crypto.DecodePublicKey(publicKey), signedBytes, Signature ); Array.Reverse(Signature); bool Check3 = U2F.Crypto.CheckSignature( U2F.Crypto.DecodePublicKey(publicKey), signedBytes, Signature ); |
Author: | bbartlett [ Thu Mar 12, 2015 1:18 am ] |
Post subject: | Re: U2F Signing algorithm with .NET lib |
The FIDO specification for RawMessages Registration section 4.3 states "The signature is to be verified by the relying party using the public key certified in the attestation certificate." Shouldn't the public key in the attestation certificate match the public key returned in the device response? The following code checks the signature during Registration. pKey1 is the public key from the device RegistrationData bytes 1-65 and pKey2 is the AttestationCertificate public key value. They are not equal (pKeyVerify is false). I try to verify the signature with pKey1 and pKey2 and both results are false. pKey1 is the value that is persisted in the database for the user device and later retrieved for authentication. public bool CheckSignature(String appId, String clientData) { byte[] signedBytes = PackBytesToSign( U2F.Crypto.Hash(appId), U2F.Crypto.Hash(clientData), KeyHandle, UserPublicKey ); ICipherParameters pKey1 = U2F.Crypto.DecodePublicKey(UserPublicKey); ICipherParameters pKey2 = AttestationCertificate.GetPublicKey(); bool pKeyVerify = (pKey1 == pKey2); bool check1 = U2F.Crypto.CheckSignature( pKey1, signedBytes, Signature ); bool check2 = U2F.Crypto.CheckSignature( pKey2, signedBytes, Signature ); return (check1 || check2); } |
Author: | bbartlett [ Thu Mar 12, 2015 2:31 am ] |
Post subject: | Re: U2F Signing algorithm with .NET lib |
Here are some values from a registration. RegistrationData (745 bytes): BQS4N1NGYUOrziL-FJEBiB4LlbzXoBrf4I9k-ltUspOOYrNDwRtYvPRcyxcxrCuWDvAOK2ngvjuEJgp2ip4nk9HwQIqdQW_lN7zWuWDJvOXri6Q4IVup3QkAsA4NV9c1TuTpfc9VeyEHyhaDTObuau5nlx9eHpGFaiF3qBEp-bc3TNUwggIcMIIBBqADAgECAgQ4Zt91MAsGCSqGSIb3DQEBCzAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowKzEpMCcGA1UEAwwgWXViaWNvIFUyRiBFRSBTZXJpYWwgMTM4MzExNjc4NjEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ3jfx0DHOblHJO09Ujubh2gQZWwT3ob6-uzzjZD1XiyAob_gsw3FOzXefQRblty48r-U-o4LkDFjx_btwuSHtxoxIwEDAOBgorBgEEAYLECgEBBAAwCwYJKoZIhvcNAQELA4IBAQIaR2TKAInPkq24f6hIU45yzD79uzR5KUMEe4IWqTm69METVio0W2FHWXlpeUe85nGqanwGeW7U67G4_WAnGbcd6zz2QumNsdlmb_AebbdPRa95Z8BG1ub_S04JoxQYNLaa8WRlzN7POgqAnAqkmnsZQ_W9Tj2uO9zP3mpxOkkmnqz7P5zt4Lp5xrv7p15hGOIPD5V-ph7tUmiCJsq0LfeRA36X7aXi32Ap0rt_wyfnRef59YYr7SmwaMuXKjbIZSLesscZZTMzXd-uuLb6DbUCasqEVBkGGqTRfAcOmPov1nHUrNDCkOR0obR4PsJG4PiamIfApNeoXGYpGbok6nucMEQCIHA2eDMSEyuCBPKfBD5PkZROH_qvk8_57WNIV3rzdMupAiA8f3mPVHvO-DWh5xwl-6zZrTHBtadEBlxdogCvkWCiQw Header (1 byte): 0x05 Public Key (65 bytes): BLg3U0ZhQ6vOIv4UkQGIHguVvNegGt_gj2T6W1Syk45is0PBG1i89FzLFzGsK5YO8A4raeC-O4QmCnaKnieT0fA Key handle length (1 byte): 0x40 (64) Key handle (64 bytes): ip1Bb-U3vNa5YMm85euLpDghW6ndCQCwDg1X1zVO5Ol9z1V7IQfKFoNM5u5q7meXH14ekYVqIXeoESn5tzdM1Q X509 certificate (544 bytes): Signature (70 bytes): MEQCIHA2eDMSEyuCBPKfBD5PkZROH_qvk8_57WNIV3rzdMupAiA8f3mPVHvO-DWh5xwl-6zZrTHBtadEBlxdogCvkWCiQw 1+65+1+64+544+70 = 745 Verify Register Signature BytesToSign (194 bytes): 1 byte 0x00 32 bytes hash(appId) http://localhost:52701 32 bytes hash(clientdata): {\"typ\":\"navigator.id.finishEnrollment\",\"challenge\":\"2ScxHgONm6HWi-69dl-u8MHJStWczxjyqVYc1Jywnyc\",\"origin\":\"http://localhost:52701\",\"cid_pubkey\":\"\"} 64 bytes key handle: ip1Bb-U3vNa5YMm85euLpDghW6ndCQCwDg1X1zVO5Ol9z1V7IQfKFoNM5u5q7meXH14ekYVqIXeoESn5tzdM1Q 65 bytes public key: BLg3U0ZhQ6vOIv4UkQGIHguVvNegGt_gj2T6W1Syk45is0PBG1i89FzLFzGsK5YO8A4raeC-O4QmCnaKnieT0fA BytesToSign (194 bytes): ABDSG0CFmfWR62OPK5J72_z4x0ukRjfsJHCjP59IIwh8zGCun1fxQS1HuBc1lXcbueGEoLZ2ToBbDJUUnzvCbwCKnUFv5Te81rlgybzl64ukOCFbqd0JALAODVfXNU7k6X3PVXshB8oWg0zm7mruZ5cfXh6RhWohd6gRKfm3N0zVBLg3U0ZhQ6vOIv4UkQGIHguVvNegGt_gj2T6W1Syk45is0PBG1i89FzLFzGsK5YO8A4raeC-O4QmCnaKnieT0fA VerifySignature fails!! |
Author: | henrik [ Thu Mar 12, 2015 1:33 pm ] |
Post subject: | Re: U2F Signing algorithm with .NET lib |
I fed the Java library with the data you posted above. It verifies the signature successfully. KeyHandle and publicKey are the same but not bytesToSign: Code: // bytesToSign from the .NET library: ABDSG0CFmfWR62OPK5J72_z4x0ukRjfsJHCjP59IIwh8zGCun1fxQS1HuBc1lXcbueGEoLZ2ToBbDJUUnzvCbwC KnUFv5Te81rlgybzl64ukOCFbqd0JALAODVfXNU7k6X3PVXshB8oWg0zm7mruZ5cfXh6RhWohd6gRKfm3N0zVBLg3U0ZhQ6vOIv4UkQGIHguVvNegGt_gj2T6W1Syk45is0PBG1i89FzLFzGsK5YO8A4raeC-O4QmCnaKnieT0fA // bytesToSign from the Java library: ANZ1LeIhomdsUMHDU4Qb4PY4BpyEjPsNyt7HvXLKEp-7LBQ8iUMFH6ZfAucUwHa0rwO23Uwnzcdp5bWNQ3T57Xe KnUFv5Te81rlgybzl64ukOCFbqd0JALAODVfXNU7k6X3PVXshB8oWg0zm7mruZ5cfXh6RhWohd6gRKfm3N0zVBLg3U0ZhQ6vOIv4UkQGIHguVvNegGt_gj2T6W1Syk45is0PBG1i89FzLFzGsK5YO8A4raeC-O4QmCnaKnieT0fA I added line breaks to highlight which parts that differs. |
Author: | henrik [ Thu Mar 12, 2015 1:39 pm ] |
Post subject: | Re: U2F Signing algorithm with .NET lib |
bbartlett wrote: 32 bytes hash(appId) http://localhost:52701 32 bytes hash(clientdata): {\"typ\":\"navigator.id.finishEnrollment\",\"challenge\":\"2ScxHgONm6HWi-69dl-u8MHJStWczxjyqVYc1Jywnyc\",\"origin\":\"http://localhost:52701\",\"cid_pubkey\":\"\"} Are you not hashing these values (you have to)? Please give me the hash of them. Judging from where the difference occurs in bytesToSign, I suspect that these two values are the problem. Maybe brucedog/u2flib's BouncyCastleCrypto.Hash(string) does not output the same as yubico/java-u2flib-server's BouncyCastleCrypto.hash(String). The correct hashes of the strings you posted are: Code: appIdHash: 1nUt4iGiZ2xQwcNThBvg9jgGnISM-w3K3se9csoSn7s
clientDataHash: LBQ8iUMFH6ZfAucUwHa0rwO23Uwnzcdp5bWNQ3T57Xc |
Page 1 of 2 | All times are UTC + 1 hour |
Powered by phpBB® Forum Software © phpBB Group https://www.phpbb.com/ |