diff --git a/vertx-auth-webauthn/src/main/java/io/vertx/ext/auth/webauthn/impl/attestation/TPMAttestation.java b/vertx-auth-webauthn/src/main/java/io/vertx/ext/auth/webauthn/impl/attestation/TPMAttestation.java index c9e367a2b..2818c593a 100644 --- a/vertx-auth-webauthn/src/main/java/io/vertx/ext/auth/webauthn/impl/attestation/TPMAttestation.java +++ b/vertx-auth-webauthn/src/main/java/io/vertx/ext/auth/webauthn/impl/attestation/TPMAttestation.java @@ -168,13 +168,21 @@ public AttestationCertificates validate(WebAuthnOptions options, MetaData metada } } else if (pubArea.getType() == TPM_ALG_ECC) { // extract the RSA parameters from the COSE CBOR - byte[] crv = base64UrlDecode(cosePublicKey.getString("-1")); - byte[] x = base64UrlDecode(cosePublicKey.getString("-2")); - byte[] y = base64UrlDecode(cosePublicKey.getString("-3")); - // Do some bit shifting to get to an integer - if (pubArea.getCurveID() != crv[0] + (crv[1] << 8)) { + int crv; + if(cosePublicKey.getValue("-1") instanceof String){ + byte[] byteCrv = base64UrlDecode(cosePublicKey.getString("-1")); + // Do some bit shifting to get to an integer + crv = byteCrv[0] + (byteCrv[1] << 8); + } + else{ + crv = cosePublicKey.getInteger("-1"); + } + if (mapCurveId(pubArea.getCurveID()) != crv) { throw new AttestationException("Unexpected public key crv"); } + byte[] x = base64UrlDecode(cosePublicKey.getString("-2")); + byte[] y = base64UrlDecode(cosePublicKey.getString("-3")); + // 4. Check that pubArea.unique is set to the same public key, // as the one in “authData” struct. if (!MessageDigest.isEqual(pubArea.getUnique(), Buffer.buffer().appendBytes(x).appendBytes(y).getBytes())) { @@ -410,4 +418,13 @@ public AttestationCertificates validate(WebAuthnOptions options, MetaData metada throw new AttestationException(e); } } + // TPM-2.0-1.83-Part-2-Structures. 6.4 Table10 + private int mapCurveId(int curveId){ + switch (curveId){ + case 3: return 1; //ECC_NIST_P256 + case 4: return 2; //ECC_NIST_P384 + case 5: return 3; //ECC_NIST_P521 + } + return 0; + } } diff --git a/vertx-auth-webauthn/src/main/java/io/vertx/ext/auth/webauthn/impl/attestation/tpm/PubArea.java b/vertx-auth-webauthn/src/main/java/io/vertx/ext/auth/webauthn/impl/attestation/tpm/PubArea.java index 4d3145d41..1bab339b2 100644 --- a/vertx-auth-webauthn/src/main/java/io/vertx/ext/auth/webauthn/impl/attestation/tpm/PubArea.java +++ b/vertx-auth-webauthn/src/main/java/io/vertx/ext/auth/webauthn/impl/attestation/tpm/PubArea.java @@ -53,6 +53,10 @@ public PubArea(Buffer pubBuffer) { pos += 2; exponent = pubBuffer.getUnsignedInt(pos); pos += 4; + // Slice out unique of dynamic length + len = pubBuffer.getUnsignedShort(pos); + pos += 2; + unique = pubBuffer.getBytes(pos, pos + len); } else if (type == TPM_ALG_ECC) { // read 8 bytes symmetric = pubBuffer.getUnsignedShort(pos); @@ -60,17 +64,23 @@ public PubArea(Buffer pubBuffer) { scheme = pubBuffer.getUnsignedShort(pos); pos += 2; curveID = pubBuffer.getUnsignedShort(pos); - pos += 4; + pos+=2; kdf = pubBuffer.getUnsignedShort(pos); pos += 2; + //Unique structure: x length + x data + y length + y data + len = pubBuffer.getUnsignedShort(pos); + pos += 2; + byte[] uniqueX = pubBuffer.getBytes(pos, pos + len); + pos += len; + len = pubBuffer.getUnsignedShort(pos); + pos += 2; + byte[] uniqueY = pubBuffer.getBytes(pos, pos + len); + unique = new byte[uniqueX.length + uniqueY.length]; + System.arraycopy(uniqueX, 0, unique, 0, uniqueX.length); + System.arraycopy(uniqueY, 0, unique, uniqueX.length, uniqueY.length); } else { throw new IllegalArgumentException("Unexpected type: " + type); } - - // Slice out unique of dynamic length - len = pubBuffer.getUnsignedShort(pos); - pos += 2; - unique = pubBuffer.getBytes(pos, pos + len); } public int getType() { diff --git a/vertx-auth-webauthn/src/test/java/io/vertx/tests/attestation/AttestationTest.java b/vertx-auth-webauthn/src/test/java/io/vertx/tests/attestation/AttestationTest.java index 005cc9ee3..8c0572ed0 100644 --- a/vertx-auth-webauthn/src/test/java/io/vertx/tests/attestation/AttestationTest.java +++ b/vertx-auth-webauthn/src/test/java/io/vertx/tests/attestation/AttestationTest.java @@ -271,6 +271,38 @@ public void testTPM2Attestation(TestContext should) { }); } + @Test + public void testTPMAttestationWithECC(TestContext should){ + final Async test = should.async(); + + WebAuthn webAuthN = WebAuthn.create( + rule.vertx(), + new WebAuthnOptions().setRelyingParty(new RelyingParty().setName("FIDO Examples Corporation"))) + .authenticatorFetcher(database::fetch) + .authenticatorUpdater(database::store); + + JsonObject packedFullAttestationWebAuthnSample = new JsonObject() + .put("rawId", "QgnU71eJSchY96ne1hnJq_pJoweUbVssQ_THlP3MA6I") + .put("id", "QgnU71eJSchY96ne1hnJq_pJoweUbVssQ_THlP3MA6I") + .put("type", "public-key") + .put("response", new JsonObject() + .put("clientDataJSON", "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiQ3pGd2k2cVF1TDBJa3ktNURJUksyOEhzRGZTazI4cWhMODVjOThtNFYxaTk5YTllc0RWM2ZjYURuTzRucHF6NVMzTUxadVc1eEp6M2hNdDFhS1lhbGciLCJvcmlnaW4iOiJodHRwczovL3dlYmF1dGhuLmlvIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ") + .put("attestationObject", "o2NmbXRjdHBtZ2F0dFN0bXSmY2FsZzn__mNzaWdZAQBjFp5484Y4u8jdKDY3o1osD4ha5dpMbpe5gLz7oCg86KxESDgC2DQVsUzVs9guXFIUPvPFBVcV1MxqJRdKLfBUixQhmbDaZK3eYpxGjZzSTiIhQdwnxDT_vQx0yU0Cg2XC28Qe35rdWmnD-7gjcKcXFfw9YGdQPN7uQibB6jWZPtyjj2iKsWA3A5gQGR_WwJ2MwfAzZmVG8PzXn9DtwBAh5tIs8wbz8nTN3ligMqgyvorQcX0Tx1NKE8HDPvg2jqhrXVDf30TpDE90ZZWcabcK3CQ3yaBpxItBj2eaxf5aWDbRJIHPxHB2dzF3RuPGI93gERdWzWNGXIOrrt-eQPsQY3ZlcmMyLjBjeDVjglkFxDCCBcAwggOooAMCAQICEBRtpFnSuE8VlVmSdiGF_98wDQYJKoZIhvcNAQELBQAwQTE_MD0GA1UEAxM2TkNVLVNUTS1LRVlJRC1GQjE3RDcwRDczNDg3MEU5MTlDNEU4RTYwMzk3NUU2NjRFMEU0M0RFMB4XDTI0MDcyNDA3MjIzMVoXDTI3MDYwMzE5NDAzMlowADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOJypYVvKkZHhV1DYAfzwfXrrffdRh9WVNtnn6L9b1MhPVzOHq7FnS-WIoSPqmbG50JdmmUtRkEtgKL6Dp2uHauh_2ahSwbRdVYMHV7emb9oL9zw6h7GBJ9JLuFBOlG50mZmc2S5jBYAvyuwL6NoX5vT3ZBevqdFyeZGIG6wDyg3UZ5Nu1vBIZ_fYLy26QDf-ZSwy7lcyiDxuJ62mTSpFCXNTQM1-5ok1IftKujs_GIDt_VD2RTwJvLvSpShp9tbncXVMGVf4sJMYdqHdy1O7qc6lgeRQbBCrNsIJ4tHKXDU1as6hOcRg8plOMRF9hQaacgxrowUSf9LuqSz5hczOWsCAwEAAaOCAfMwggHvMA4GA1UdDwEB_wQEAwIHgDAMBgNVHRMBAf8EAjAAMG0GA1UdIAEB_wRjMGEwXwYJKwYBBAGCNxUfMFIwUAYIKwYBBQUHAgIwRB5CAFQAQwBQAEEAIAAgAFQAcgB1AHMAdABlAGQAIAAgAFAAbABhAHQAZgBvAHIAbQAgACAASQBkAGUAbgB0AGkAdAB5MBAGA1UdJQQJMAcGBWeBBQgDMFkGA1UdEQEB_wRPME2kSzBJMRYwFAYFZ4EFAgEMC2lkOjUzNTQ0RDIwMRcwFQYFZ4EFAgIMDFNUMzNIVFBIQUhEODEWMBQGBWeBBQIDDAtpZDowMDAxMDEwMjAfBgNVHSMEGDAWgBRaCiuOAq_sWTlHW5htW5P_P2vZ6DAdBgNVHQ4EFgQUqhXWnz_VgS-uAI-2g5Gp56kkZiwwgbIGCCsGAQUFBwEBBIGlMIGiMIGfBggrBgEFBQcwAoaBkmh0dHA6Ly9hemNzcHJvZG5jdWFpa3B1Ymxpc2guYmxvYi5jb3JlLndpbmRvd3MubmV0L25jdS1zdG0ta2V5aWQtZmIxN2Q3MGQ3MzQ4NzBlOTE5YzRlOGU2MDM5NzVlNjY0ZTBlNDNkZS9kYmQ5NjBjOS01NDgyLTQ1MjAtYTY4ZC01ODQ4MWVhMWMwNjguY2VyMA0GCSqGSIb3DQEBCwUAA4ICAQBZT2wQyij5WIF_FPUho5f19Vv0YdXwFtcl4srlD9nd8pw4O9Hn569ghU2YnkHy2oFPMi6riAQ_Ar07Q0G1CNIMJqocF-plPJy5BfyoqmCXzM4C0mKRUbZBXvao3Bag3bXHnfymzU1mr2h_f1fc0rzbWSCsdoeypoQzv57Ule2FgjWKiSa8y5pQsORp2hrM5qz2SbYHAVWsx3bXDEQhwcnD5VAWnmhzqf4GMy4VcuJyPZ9ssoiTKKN1fUoLNAJlgzo5i63vGeOmBfQeSyVv4iWIjrmY8oyVqG-luCAita_BTCaySHOjPGr8oFQpO-zqvf4PVW1O8dNdRwycN6rH0fnvO0cR8S0DZl3DuRDC2zd-UyPWJsiIYFOQjr6FJpRW9qj6nlZKg-0eu7YNP8MMhjQJx3wKGnDPcL45GdbDiojGh71cb3YIUtlr8XVnX9vtxIlNiHFDRYVp0JwMdKO0vnIbwSYzURya4h4jL58H5b38BbrWX3hzkYua0uFS-A8LqSa-xpHE00WaJuFBoMscXWl-QrNtKIwQjQKbF8Hwy0U88kRa4v6uoI0rORAdoeGyZd71QSLzorMaOuCcM5PrnK0G9FO1uQ-Zu-s8GFZFWFOkTB6Mz1UIA_zcdauIq_rK43rB_w-djELSCE-Ivq1MTRH6x4BXQhFGW9tqZdc2JHXVDVkG7zCCBuswggTToAMCAQICEzMAAAU4KrGurbiNtnoAAAAABTgwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBUUE0gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNDAeFw0yMTA2MDMxOTQwMzJaFw0yNzA2MDMxOTQwMzJaMEExPzA9BgNVBAMTNk5DVS1TVE0tS0VZSUQtRkIxN0Q3MEQ3MzQ4NzBFOTE5QzRFOEU2MDM5NzVFNjY0RTBFNDNERTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMez3a8ZBgGWY2FuNy3dQnWN7FQzCQYsu3ggB1RC_bguziH11BnJY6Qa607SI9IGwNliaSTfwqKiUXvOUD_UYPlXez8yr9lAJOnl-EH6pkmvmhk2bI4ArW5TGZ6948zXj33oEHrQ2mj_aVkWfofGwuD-jpCG2EESuPyFyatnkTvldEqp4EcMj2P0A-S28AYhGZW70oy4oRJoRWX6SEfnnA1kMAsxrbYuI1J76_qAEwPKVDz1-zHl0KM2nMouEjnn2m5O94auzviuN5rYO2kuOUkRBrccYJ3acoFlNzLdrvBdrSQEqMHuvYIk11x5B19xHBF0O3TqSBWcdE-5zeyFhbrLMJvb130NQxiBQQwZ-_oOvCfOvCQ7Zj_sa9bmCb4XamAY0Tss005tnlvr1bP6q22DrsLx0yeQ87iEFh49CG6riYv6SCbDKmeWOYp19anA5xKP6RXaSnouja9BUKWc4MhzbLiv0x7lLPzxuFJvBd6wj8Vwa75hsnn8n_JrN_CvyljAsXmDFeGHhmd1UyfemOAA6C1MeLxGDiIMeOy5bbWPqOGc5i3YxYjaIVPsgwymBhcwglfIemHj2ik8yXjoCk9StaznVUgP62uMALYhPwzdAgUymkvARgAI7DlNpzDodToquPXod6Z0OCZKT8pRUq3bwpxgJeXDUVIuRTpkyqcRAgMBAAGjggGOMIIBijAOBgNVHQ8BAf8EBAMCAoQwGwYDVR0lBBQwEgYJKwYBBAGCNxUkBgVngQUIAzAWBgNVHSAEDzANMAsGCSsGAQQBgjcVHzASBgNVHRMBAf8ECDAGAQH_AgEAMB0GA1UdDgQWBBRaCiuOAq_sWTlHW5htW5P_P2vZ6DAfBgNVHSMEGDAWgBR6jArOL0hiF-KU0a5VwVLscXSkVjBwBgNVHR8EaTBnMGWgY6Bhhl9odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUUE0lMjBSb290JTIwQ2VydGlmaWNhdGUlMjBBdXRob3JpdHklMjAyMDE0LmNybDB9BggrBgEFBQcBAQRxMG8wbQYIKwYBBQUHMAKGYWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVFBNJTIwUm9vdCUyMENlcnRpZmljYXRlJTIwQXV0aG9yaXR5JTIwMjAxNC5jcnQwDQYJKoZIhvcNAQELBQADggIBABIqgccy5CUJk5qWtW2E5iZMVuYoMh0AyLai0K3KBF23csWE_NQpnJiUY6wZwW1h9Gr99PURuNUzprDf0lZ77IkTjUderZ3zTw8pgH2qP5UPSk9-o-0ciyZfpsOuywFYdEtcK2ig3ZkESffWZWyqtfX8qMrE0TVHQJ6UapROxiZwUgX_1izil-BxfpNPm3x_lFfktQax6sIQODasSRmd9WDBRaO_3aPTHAHn5tTYC8pI_pRM94J7MUnbRKVXfuEhRynXF7sfVjXuHoDi22sgIQP6mUJ1uH-LxaaAbnv77oQ3tQL5edSG-Mxjj60-lWEVfoTM8FRBbUX7Zs0NTVXWat_ms0ZHbSnFzeO7BQf4EEAnVrfEpj7Vv16V-ZbmVNeqUBoKrEqpVLHkbfD3mTndavPbXt4ojs1UBgdtDpzO6YaUCx5HYAVuPUIbH1UFB0VM_B_yZkkP1lSWLnHZWy1WKTa7shwqLv2-KnrijF4esEH8CbAPwXJ6g8agmibD0XVxbReEtWpBKcUFfo6eFilxrj1m4z4GvQS-BWS1zD1DWyvMotFAdKAHFUXZ9Kmmp6GownqT6c2GOlbVtG64Ws5oU9VsgQMsIxMLtkBqDDE2oVEvyD5gzYbpogC3Fv5MiCPbwvhtSvOZJ431jMbnVh-0fc-NB3311sd2vFGxzBXfGOMOZ3B1YkFyZWFYdgAjAAsABAByACCd_8vzbDg65pn7mGjcbcuJ1xU4hL4oA5IsEkFYv60irgAQABAAAwAQACC_FQYfuRB_d8Ip477iHrFU9bTr42yvVP8c-mFzDj4L6gAgN25UJ9Luw9OK5y3bvh8H8zQTZlxzMCUAlODzMudenftoY2VydEluZm9Yof9UQ0eAFwAiAAs9bWDORZijXjf1k12yjvAmfNwDlPL8eZkYgArYSmwTvQAURFCOcGTLqZlRBR1eTQy2WJDPpAQAAAAAIdPP7p24PiE03sl4Abaadv2IcEGkACIAC2baR9eR-Da6JY8xOmkHxE8YlHbyWAY9c83UcnNkg6pzACIAC8suHiNiE5awWdCquSRqOtTk7xNywpVD9o0SqMWf4VEyaGF1dGhEYXRhWKR0puqSE8mcL3SyJJKzIM9AJiqUwalQoDl_KSULYIQe8EUAAAAACJhwWMrcS4G24TDeUNy-lgAgQgnU71eJSchY96ne1hnJq_pJoweUbVssQ_THlP3MA6KlAQIDJiABIVggvxUGH7kQf3fCKeO-4h6xVPW06-Nsr1T_HPphcw4-C-oiWCA3blQn0u7D04rnLdu-HwfzNBNmXHMwJQCU4PMy516d-w")); + + webAuthN.authenticate( + new WebAuthnCredentials() + .setUsername("rafal") + .setOrigin("https://webauthn.io") + .setDomain("webauthn.io") + .setWebauthn(packedFullAttestationWebAuthnSample) + .setChallenge("CzFwi6qQuL0Iky-5DIRK28HsDfSk28qhL85c98m4V1i99a9esDV3fcaDnO4npqz5S3MLZuW5xJz3hMt1aKYalg")) + .onFailure(should::fail) + .onSuccess(user -> { + should.assertNotNull(user); + test.complete(); + }); + } + @Test public void testBrokenSafetyNetAttestation(TestContext should) { final Async test = should.async();