|
| 1 | +import XCTest |
| 2 | +import SwiftCBOR |
| 3 | +@testable import WebAuthn |
| 4 | + |
| 5 | +// swiftlint:disable line_length |
| 6 | + |
| 7 | +// swiftlint:disable:next type_name |
| 8 | +final class ParsedAuthenticatorAttestationResponseTests: XCTestCase { |
| 9 | + let realClientDataJSON = "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiY21GdVpHOXRVM1J5YVc1blJuSnZiVk5sY25abGNnIiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwiY3Jvc3NPcmlnaW4iOmZhbHNlLCJvdGhlcl9rZXlzX2Nhbl9iZV9hZGRlZF9oZXJlIjoiZG8gbm90IGNvbXBhcmUgY2xpZW50RGF0YUpTT04gYWdhaW5zdCBhIHRlbXBsYXRlLiBTZWUgaHR0cHM6Ly9nb28uZ2wveWFiUGV4In0" |
| 10 | + let realAttestationObject = "o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEcwRQIgNTRtpI_SOOZVzU1pN_4cX-osqUPiHMOW48qqq91DXfUCIQC-MHiaIxt2OdIxgqYnyUDHceevNOMfPibenabQGvXgjGhhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAAK3OAAI1vMYKZIsLJfHwVQMAIDo-5W3Kur7A7y9Lfw7ijhExfCz3_5coMEQNY_y6p-JrpQECAyYgASFYIJr_yLoYbYWgcf7aQcd7pcjUj-3o8biafWQH28WijQSvIlggPI2KqqRQ26KKuFaJ0yH7nouCBrzHu8qRONW-CPa9VDM" |
| 11 | + |
| 12 | + func testInitFromRawResponseFailsWithInvalidClientDataJSON() throws { |
| 13 | + XCTAssertThrowsError(try parseResponse( |
| 14 | + clientDataJSON: "a", // this isn't base64 decodable, so parsing should fail |
| 15 | + attestationObject: "" |
| 16 | + )) { error in |
| 17 | + XCTAssertEqual(error as? WebAuthnError, .invalidClientDataJSON) |
| 18 | + } |
| 19 | + } |
| 20 | + |
| 21 | + func testInitFromRawResponseFailsIfClientDataJSONDecodingFails() throws { |
| 22 | + XCTAssertThrowsError(try parseResponse( |
| 23 | + clientDataJSON: "abc", // this is base64 decodable, but will not result in a proper clientData json object |
| 24 | + attestationObject: "" |
| 25 | + )) { error in |
| 26 | + XCTAssertNotNil(error as? DecodingError) |
| 27 | + } |
| 28 | + } |
| 29 | + |
| 30 | + func testInitFromRawResponseFailsIfAttestationObjectIsNotBase64() throws { |
| 31 | + XCTAssertThrowsError(try parseResponse( |
| 32 | + clientDataJSON: realClientDataJSON, |
| 33 | + attestationObject: "a" |
| 34 | + )) { error in |
| 35 | + XCTAssertEqual(error as? WebAuthnError, .invalidAttestationObject) |
| 36 | + } |
| 37 | + } |
| 38 | + |
| 39 | + func testInitFromRawResponseFailsIfAuthDataIsInvalid() throws { |
| 40 | + let attestationObjectWithInvalidAuthData = "A363666D74667061636B65646761747453746D74A263616C67266373696758473045022035346DA48FD238E655CD4D6937FE1C5FEA2CA943E21CC396E3CAAAABDD435DF5022100BE30789A231B7639D23182A627C940C771E7AF34E31F3E26DE9DA6D01AF5E08C68617574684461746101" |
| 41 | + XCTAssertThrowsError(try parseResponse( |
| 42 | + clientDataJSON: realClientDataJSON, |
| 43 | + attestationObject: attestationObjectWithInvalidAuthData |
| 44 | + )) { error in |
| 45 | + XCTAssertEqual(error as? WebAuthnError, .invalidAuthData) |
| 46 | + } |
| 47 | + } |
| 48 | + |
| 49 | + func testInitFromRawResponseFailsIfFmtIsInvalid() throws { |
| 50 | + let attestationObjectWithInvalidFmt = "o2NmbXQBZ2F0dFN0bXSiY2FsZyZjc2lnWEcwRQIgNTRtpI_SOOZVzU1pN_4cX-osqUPiHMOW48qqq91DXfUCIQC-MHiaIxt2OdIxgqYnyUDHceevNOMfPibenabQGvXgjGhhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAAK3OAAI1vMYKZIsLJfHwVQMAIDo-5W3Kur7A7y9Lfw7ijhExfCz3_5coMEQNY_y6p-JrpQECAyYgASFYIJr_yLoYbYWgcf7aQcd7pcjUj-3o8biafWQH28WijQSvIlggPI2KqqRQ26KKuFaJ0yH7nouCBrzHu8qRONW-CPa9VDM" |
| 51 | + XCTAssertThrowsError(try parseResponse( |
| 52 | + clientDataJSON: realClientDataJSON, |
| 53 | + attestationObject: attestationObjectWithInvalidFmt |
| 54 | + )) { error in |
| 55 | + XCTAssertEqual(error as? WebAuthnError, .invalidFmt) |
| 56 | + } |
| 57 | + } |
| 58 | + |
| 59 | + func testInitFromRawResponseFailsIfAttStmtIsMissing() throws { |
| 60 | + let attestationObjectWithMissingAttStmt = "omNmbXRmcGFja2VkaGF1dGhEYXRhWKRJlg3liA6MaHQ0Fw9kdmBbj-SuuaKGMseZXPO6gx2XY0UAAAAArc4AAjW8xgpkiwsl8fBVAwAgOj7lbcq6vsDvL0t_DuKOETF8LPf_lygwRA1j_Lqn4mulAQIDJiABIVggmv_IuhhthaBx_tpBx3ulyNSP7ejxuJp9ZAfbxaKNBK8iWCA8jYqqpFDbooq4VonTIfuei4IGvMe7ypE41b4I9r1UMw" |
| 61 | + XCTAssertThrowsError(try parseResponse( |
| 62 | + clientDataJSON: realClientDataJSON, |
| 63 | + attestationObject: attestationObjectWithMissingAttStmt |
| 64 | + )) { error in |
| 65 | + XCTAssertEqual(error as? WebAuthnError, .missingAttStmt) |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | + func testInitFromRawResponseSucceeds() throws { |
| 70 | + let expectedAttestationObject = AttestationObject( |
| 71 | + authenticatorData: AuthenticatorData( |
| 72 | + relyingPartyIDHash: "49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763".hexadecimal!, |
| 73 | + flags: AuthenticatorFlags( |
| 74 | + userPresent: true, |
| 75 | + userVerified: true, |
| 76 | + isBackupEligible: false, |
| 77 | + isCurrentlyBackedUp: false, |
| 78 | + attestedCredentialData: true, |
| 79 | + extensionDataIncluded: false |
| 80 | + ), |
| 81 | + counter: 0, |
| 82 | + attestedData: AttestedCredentialData( |
| 83 | + aaguid: "adce000235bcc60a648b0b25f1f05503".hexadecimal!, |
| 84 | + credentialID: "3a3ee56dcababec0ef2f4b7f0ee28e11317c2cf7ff972830440d63fcbaa7e26b".hexadecimal!, |
| 85 | + publicKey: "a50102032620012158209affc8ba186d85a071feda41c77ba5c8d48fede8f1b89a7d6407dbc5a28d04af2258203c8d8aaaa450dba28ab85689d321fb9e8b8206bcc7bbca9138d5be08f6bd5433".hexadecimal! |
| 86 | + ), |
| 87 | + extData: nil |
| 88 | + ), |
| 89 | + rawAuthenticatorData: "49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634500000000adce000235bcc60a648b0b25f1f0550300203a3ee56dcababec0ef2f4b7f0ee28e11317c2cf7ff972830440d63fcbaa7e26ba50102032620012158209affc8ba186d85a071feda41c77ba5c8d48fede8f1b89a7d6407dbc5a28d04af2258203c8d8aaaa450dba28ab85689d321fb9e8b8206bcc7bbca9138d5be08f6bd5433".hexadecimal!, |
| 90 | + format: .packed, |
| 91 | + attestationStatement: .map([ |
| 92 | + .utf8String("sig"): .byteString("3045022035346DA48FD238E655CD4D6937FE1C5FEA2CA943E21CC396E3CAAAABDD435DF5022100BE30789A231B7639D23182A627C940C771E7AF34E31F3E26DE9DA6D01AF5E08C".hexadecimal!), |
| 93 | + .utf8String("alg"): .negativeInt(6) |
| 94 | + ]) |
| 95 | + ) |
| 96 | + |
| 97 | + let response = try parseResponse(clientDataJSON: realClientDataJSON, attestationObject: realAttestationObject) |
| 98 | + |
| 99 | + XCTAssertEqual(response.clientData.challenge, "cmFuZG9tU3RyaW5nRnJvbVNlcnZlcg") |
| 100 | + XCTAssertEqual(response.clientData.origin, "http://localhost:8080") |
| 101 | + XCTAssertEqual(response.clientData.type, .create) |
| 102 | + |
| 103 | + XCTAssertEqual(expectedAttestationObject, response.attestationObject) |
| 104 | + } |
| 105 | + |
| 106 | + private func parseResponse( |
| 107 | + clientDataJSON: URLEncodedBase64, |
| 108 | + attestationObject: URLEncodedBase64 |
| 109 | + ) throws |
| 110 | + -> ParsedAuthenticatorAttestationResponse { |
| 111 | + try ParsedAuthenticatorAttestationResponse(from: .init( |
| 112 | + clientDataJSON: clientDataJSON, |
| 113 | + attestationObject: attestationObject |
| 114 | + )) |
| 115 | + } |
| 116 | +} |
0 commit comments