@@ -15,6 +15,225 @@ namespace Test
15
15
{
16
16
public class AuthenticatorResponse
17
17
{
18
+
19
+ [ Theory ]
20
+ [ InlineData ( "https://www.passwordless.dev" , "https://www.passwordless.dev" ) ]
21
+ [ InlineData ( "https://www.passwordless.dev:443" , "https://www.passwordless.dev:443" ) ]
22
+ [ InlineData ( "https://www.passwordless.dev" , "https://www.passwordless.dev:443" ) ]
23
+ [ InlineData ( "https://www.passwordless.dev:443" , "https://www.passwordless.dev" ) ]
24
+ [ InlineData ( "https://www.passwordless.dev:443/foo/bar.html" , "https://www.passwordless.dev:443/foo/bar.html" ) ]
25
+ [ InlineData ( "https://www.passwordless.dev:443/foo/bar.html" , "https://www.passwordless.dev:443/bar/foo.html" ) ]
26
+ [ InlineData ( "https://www.passwordless.dev:443/foo/bar.html" , "https://www.passwordless.dev/bar/foo.html" ) ]
27
+ [ InlineData ( "https://www.passwordless.dev:443/foo/bar.html" , "https://www.passwordless.dev" ) ]
28
+ [ InlineData ( "ftp://www.passwordless.dev" , "ftp://www.passwordless.dev" ) ]
29
+ [ InlineData ( "ftp://www.passwordless.dev:8080" , "ftp://www.passwordless.dev:8080" ) ]
30
+ [ InlineData ( "http://127.0.0.1" , "http://127.0.0.1" ) ]
31
+ [ InlineData ( "http://localhost" , "http://localhost" ) ]
32
+ [ InlineData ( "https://127.0.0.1:80" , "https://127.0.0.1:80" ) ]
33
+ [ InlineData ( "http://localhost:80" , "http://localhost:80" ) ]
34
+ [ InlineData ( "http://127.0.0.1:443" , "http://127.0.0.1:443" ) ]
35
+ [ InlineData ( "http://localhost:443" , "http://localhost:443" ) ]
36
+ [ InlineData ( "android:apk-key-hash:Ea3dD4m7ccbwcw+a27/D547hfwYra2gKE4lIBbBjCTU" , "android:apk-key-hash:Ea3dD4m7ccbwcw+a27/D547hfwYra2gKE4lIBbBjCTU" ) ]
37
+ [ InlineData ( "lorem:ipsum:dolor" , "lorem:ipsum:dolor" ) ]
38
+ [ InlineData ( "lorem:/ipsum:4321" , "lorem:/ipsum:4321" ) ]
39
+ [ InlineData ( "lorem://ipsum:1234" , "lorem://ipsum:1234" ) ]
40
+ [ InlineData ( "lorem://ipsum:9876/sit" , "lorem://ipsum:9876/sit" ) ]
41
+ [ InlineData ( "foo://bar:321/path/" , "foo://bar:321/path/" ) ]
42
+ [ InlineData ( "foo://bar:321/path" , "foo://bar:321/path" ) ]
43
+ [ InlineData ( "http://[0:0:0:0:0:0:0:1]" , "http://[0:0:0:0:0:0:0:1]" ) ]
44
+ [ InlineData ( "http://[0:0:0:0:0:0:0:1]" , "http://[0:0:0:0:0:0:0:1]:80" ) ]
45
+ [ InlineData ( "https://[0:0:0:0:0:0:0:1]" , "https://[0:0:0:0:0:0:0:1]" ) ]
46
+ [ InlineData ( "https://[0:0:0:0:0:0:0:1]" , "https://[0:0:0:0:0:0:0:1]:443" ) ]
47
+ public async Task TestAuthenticatorOrigins ( string origin , string expectedOrigin )
48
+ {
49
+ var challenge = RandomGenerator . Default . GenerateBytes ( 128 ) ;
50
+ var rp = origin ;
51
+ var acd = new AttestedCredentialData ( ( "00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-40-FE-6A-32-63-BE-37-D1-01-B1-2E-57-CA-96-6C-00-22-93-E4-19-C8-CD-01-06-23-0B-C6-92-E8-CC-77-12-21-F1-DB-11-5D-41-0F-82-6B-DB-98-AC-64-2E-B1-AE-B5-A8-03-D1-DB-C1-47-EF-37-1C-FD-B1-CE-B0-48-CB-2C-A5-01-02-03-26-20-01-21-58-20-A6-D1-09-38-5A-C7-8E-5B-F0-3D-1C-2E-08-74-BE-6D-BB-A4-0B-4F-2A-5F-2F-11-82-45-65-65-53-4F-67-28-22-58-20-43-E1-08-2A-F3-13-5B-40-60-93-79-AC-47-42-58-AA-B3-97-B8-86-1D-E4-41-B4-4E-83-08-5D-1C-6B-E0-D0" ) . Split ( '-' ) . Select ( c => Convert . ToByte ( c , 16 ) ) . ToArray ( ) ) ;
52
+ var authData = new AuthenticatorData (
53
+ SHA256 . Create ( ) . ComputeHash ( Encoding . UTF8 . GetBytes ( origin ) ) ,
54
+ AuthenticatorFlags . UP | AuthenticatorFlags . AT ,
55
+ 0 ,
56
+ acd ,
57
+ null
58
+ ) . ToByteArray ( ) ;
59
+ var clientDataJson = Encoding . UTF8 . GetBytes (
60
+ JsonConvert . SerializeObject
61
+ (
62
+ new
63
+ {
64
+ Type = "webauthn.create" ,
65
+ Challenge = challenge ,
66
+ Origin = rp ,
67
+ }
68
+ )
69
+ ) ;
70
+ var rawResponse = new AuthenticatorAttestationRawResponse
71
+ {
72
+ Type = PublicKeyCredentialType . PublicKey ,
73
+ Id = new byte [ ] { 0xf1 , 0xd0 } ,
74
+ RawId = new byte [ ] { 0xf1 , 0xd0 } ,
75
+ Response = new AuthenticatorAttestationRawResponse . ResponseData ( )
76
+ {
77
+ AttestationObject = CBORObject . NewMap ( ) . Add ( "fmt" , "none" ) . Add ( "attStmt" , CBORObject . NewMap ( ) ) . Add ( "authData" , authData ) . EncodeToBytes ( ) ,
78
+ ClientDataJson = clientDataJson
79
+ } ,
80
+ } ;
81
+
82
+ var origChallenge = new CredentialCreateOptions
83
+ {
84
+ Attestation = AttestationConveyancePreference . Direct ,
85
+ AuthenticatorSelection = new AuthenticatorSelection
86
+ {
87
+ AuthenticatorAttachment = AuthenticatorAttachment . CrossPlatform ,
88
+ RequireResidentKey = true ,
89
+ UserVerification = UserVerificationRequirement . Required ,
90
+ } ,
91
+ Challenge = challenge ,
92
+ ErrorMessage = "" ,
93
+ PubKeyCredParams = new List < PubKeyCredParam > ( )
94
+ {
95
+ new PubKeyCredParam
96
+ {
97
+ Alg = COSE . Algorithm . ES256 ,
98
+ Type = PublicKeyCredentialType . PublicKey ,
99
+ }
100
+ } ,
101
+ Rp = new PublicKeyCredentialRpEntity ( rp , rp , "" ) ,
102
+ Status = "ok" ,
103
+ User = new Fido2User
104
+ {
105
+ Name = "testuser" ,
106
+ Id = Encoding . UTF8 . GetBytes ( "testuser" ) ,
107
+ DisplayName = "Test User" ,
108
+ } ,
109
+ Timeout = 60000 ,
110
+ } ;
111
+
112
+ IsCredentialIdUniqueToUserAsyncDelegate callback = ( args ) =>
113
+ {
114
+ return Task . FromResult ( true ) ;
115
+ } ;
116
+
117
+ var lib = new Fido2 ( new Fido2Configuration ( )
118
+ {
119
+ ServerDomain = rp ,
120
+ ServerName = rp ,
121
+ Origin = expectedOrigin ,
122
+ } ) ;
123
+
124
+ var result = await lib . MakeNewCredentialAsync ( rawResponse , origChallenge , callback ) ;
125
+ }
126
+
127
+
128
+ [ Theory ]
129
+ [ InlineData ( "https://www.passwordless.dev" , "http://www.passwordless.dev" ) ]
130
+ [ InlineData ( "https://www.passwordless.dev:443" , "http://www.passwordless.dev:443" ) ]
131
+ [ InlineData ( "https://www.passwordless.dev" , "http://www.passwordless.dev:443" ) ]
132
+ [ InlineData ( "https://www.passwordless.dev:443" , "http://www.passwordless.dev" ) ]
133
+ [ InlineData ( "https://www.passwordless.dev:443/foo/bar.html" , "http://www.passwordless.dev:443/foo/bar.html" ) ]
134
+ [ InlineData ( "https://www.passwordless.dev:443/foo/bar.html" , "http://www.passwordless.dev:443/bar/foo.html" ) ]
135
+ [ InlineData ( "https://www.passwordless.dev:443/foo/bar.html" , "http://www.passwordless.dev/bar/foo.html" ) ]
136
+ [ InlineData ( "https://www.passwordless.dev:443/foo/bar.html" , "http://www.passwordless.dev" ) ]
137
+ [ InlineData ( "ftp://www.passwordless.dev" , "ftp://www.passwordless.dev:80" ) ]
138
+ [ InlineData ( "ftp://www.passwordless.dev:8080" , "ftp://www.passwordless.dev:8081" ) ]
139
+ [ InlineData ( "https://127.0.0.1" , "http://127.0.0.1" ) ]
140
+ [ InlineData ( "https://localhost" , "http://localhost" ) ]
141
+ [ InlineData ( "https://127.0.0.1:80" , "https://127.0.0.1:81" ) ]
142
+ [ InlineData ( "http://localhost:80" , "http://localhost:82" ) ]
143
+ [ InlineData ( "http://127.0.0.1:443" , "http://127.0.0.1:444" ) ]
144
+ [ InlineData ( "http://localhost:443" , "http://localhost:444" ) ]
145
+ [ InlineData ( "android:apk-key-hash:Ea3dD4m7ccbwcw+a27/D547hfwYra2gKE4lIBbBjCTU" , "android:apk-key-hash:Ae3dD4m7ccbwcw+a27/D547hfwYra2gKE4lIBbBjCTU" ) ]
146
+ [ InlineData ( "lorem:ipsum:dolor" , "lorem:dolor:ipsum" ) ]
147
+ [ InlineData ( "lorem:/ipsum:4321" , "lorem:/ipsum:4322" ) ]
148
+ [ InlineData ( "lorem://ipsum:1234" , "lorem://ipsum:1235" ) ]
149
+ [ InlineData ( "lorem://ipsum:9876/sit" , "lorem://ipsum:9877/sit" ) ]
150
+ [ InlineData ( "foo://bar:321/path/" , "foo://bar:322/path/" ) ]
151
+ [ InlineData ( "foo://bar:321/path" , "foo://bar:322/path" ) ]
152
+ [ InlineData ( "https://[0:0:0:0:0:0:0:1]" , "http://[0:0:0:0:0:0:0:1]" ) ]
153
+ [ InlineData ( "https://[0:0:0:0:0:0:0:1]" , "http://[0:0:0:0:0:0:0:1]:80" ) ]
154
+ [ InlineData ( "http://[0:0:0:0:0:0:0:1]" , "https://[0:0:0:0:0:0:0:1]" ) ]
155
+ [ InlineData ( "http://[0:0:0:0:0:0:0:1]" , "https://[0:0:0:0:0:0:0:1]:443" ) ]
156
+ public void TestAuthenticatorOriginsFail ( string origin , string expectedOrigin )
157
+ {
158
+ var challenge = RandomGenerator . Default . GenerateBytes ( 128 ) ;
159
+ var rp = origin ;
160
+ var acd = new AttestedCredentialData ( ( "00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-40-FE-6A-32-63-BE-37-D1-01-B1-2E-57-CA-96-6C-00-22-93-E4-19-C8-CD-01-06-23-0B-C6-92-E8-CC-77-12-21-F1-DB-11-5D-41-0F-82-6B-DB-98-AC-64-2E-B1-AE-B5-A8-03-D1-DB-C1-47-EF-37-1C-FD-B1-CE-B0-48-CB-2C-A5-01-02-03-26-20-01-21-58-20-A6-D1-09-38-5A-C7-8E-5B-F0-3D-1C-2E-08-74-BE-6D-BB-A4-0B-4F-2A-5F-2F-11-82-45-65-65-53-4F-67-28-22-58-20-43-E1-08-2A-F3-13-5B-40-60-93-79-AC-47-42-58-AA-B3-97-B8-86-1D-E4-41-B4-4E-83-08-5D-1C-6B-E0-D0" ) . Split ( '-' ) . Select ( c => Convert . ToByte ( c , 16 ) ) . ToArray ( ) ) ;
161
+ var authData = new AuthenticatorData (
162
+ SHA256 . Create ( ) . ComputeHash ( Encoding . UTF8 . GetBytes ( origin ) ) ,
163
+ AuthenticatorFlags . UP | AuthenticatorFlags . AT ,
164
+ 0 ,
165
+ acd ,
166
+ null
167
+ ) . ToByteArray ( ) ;
168
+ var clientDataJson = Encoding . UTF8 . GetBytes (
169
+ JsonConvert . SerializeObject
170
+ (
171
+ new
172
+ {
173
+ Type = "webauthn.create" ,
174
+ Challenge = challenge ,
175
+ Origin = rp ,
176
+ }
177
+ )
178
+ ) ;
179
+ var rawResponse = new AuthenticatorAttestationRawResponse
180
+ {
181
+ Type = PublicKeyCredentialType . PublicKey ,
182
+ Id = new byte [ ] { 0xf1 , 0xd0 } ,
183
+ RawId = new byte [ ] { 0xf1 , 0xd0 } ,
184
+ Response = new AuthenticatorAttestationRawResponse . ResponseData ( )
185
+ {
186
+ AttestationObject = CBORObject . NewMap ( ) . Add ( "fmt" , "none" ) . Add ( "attStmt" , CBORObject . NewMap ( ) ) . Add ( "authData" , authData ) . EncodeToBytes ( ) ,
187
+ ClientDataJson = clientDataJson
188
+ } ,
189
+ } ;
190
+
191
+ var origChallenge = new CredentialCreateOptions
192
+ {
193
+ Attestation = AttestationConveyancePreference . Direct ,
194
+ AuthenticatorSelection = new AuthenticatorSelection
195
+ {
196
+ AuthenticatorAttachment = AuthenticatorAttachment . CrossPlatform ,
197
+ RequireResidentKey = true ,
198
+ UserVerification = UserVerificationRequirement . Required ,
199
+ } ,
200
+ Challenge = challenge ,
201
+ ErrorMessage = "" ,
202
+ PubKeyCredParams = new List < PubKeyCredParam > ( )
203
+ {
204
+ new PubKeyCredParam
205
+ {
206
+ Alg = COSE . Algorithm . ES256 ,
207
+ Type = PublicKeyCredentialType . PublicKey ,
208
+ }
209
+ } ,
210
+ Rp = new PublicKeyCredentialRpEntity ( rp , rp , "" ) ,
211
+ Status = "ok" ,
212
+ User = new Fido2User
213
+ {
214
+ Name = "testuser" ,
215
+ Id = Encoding . UTF8 . GetBytes ( "testuser" ) ,
216
+ DisplayName = "Test User" ,
217
+ } ,
218
+ Timeout = 60000 ,
219
+ } ;
220
+
221
+ IsCredentialIdUniqueToUserAsyncDelegate callback = ( args ) =>
222
+ {
223
+ return Task . FromResult ( true ) ;
224
+ } ;
225
+
226
+ var lib = new Fido2 ( new Fido2Configuration ( )
227
+ {
228
+ ServerDomain = rp ,
229
+ ServerName = rp ,
230
+ Origin = expectedOrigin ,
231
+ } ) ;
232
+
233
+ var ex = Assert . ThrowsAsync < Fido2VerificationException > ( ( ) => lib . MakeNewCredentialAsync ( rawResponse , origChallenge , callback ) ) ;
234
+ Assert . StartsWith ( "Fully qualified origin" , ex . Result . Message ) ;
235
+ }
236
+
18
237
[ Fact ]
19
238
public void TestAuthenticatorAttestationRawResponse ( )
20
239
{
0 commit comments