@@ -15,6 +15,225 @@ namespace Test
1515{
1616 public class AuthenticatorResponse
1717 {
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+
18237 [ Fact ]
19238 public void TestAuthenticatorAttestationRawResponse ( )
20239 {
0 commit comments