Skip to content

Commit 3b4478c

Browse files
authored
Add tests (#269)
1 parent 25a3013 commit 3b4478c

File tree

6 files changed

+244
-22
lines changed

6 files changed

+244
-22
lines changed

Test/Attestation/AndroidKey.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Formats.Asn1;
3-
using System.Linq;
43
using System.Security.Cryptography;
54
using System.Security.Cryptography.X509Certificates;
65
using System.Text;
@@ -88,6 +87,14 @@ public void TestAndroidKeySigNull()
8887
Assert.Equal("Invalid android-key attestation signature", ex.Result.Message);
8988
}
9089

90+
[Fact]
91+
public void TestAndroidKeyAttStmtEmpty()
92+
{
93+
_attestationObject.Set("attStmt", new CborMap { });
94+
var ex = Assert.ThrowsAsync<Fido2VerificationException>(() => MakeAttestationResponse());
95+
Assert.Equal("Attestation format android-key must have attestation statement", ex.Result.Message);
96+
}
97+
9198
[Fact]
9299
public void TestAndroidKeySigNotByteString()
93100
{

Test/Attestation/AndroidSafetyNet.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,22 @@ public void TestAndroidSafetyNetResponseJWTX5cNoKeys()
327327
Assert.Equal("No keys were present in the TOC header in SafetyNet response JWT", ex.Result.Message);
328328
}
329329

330+
[Fact]
331+
public void TestAndroidSafetyNetResponseJWTX5cInvalidString()
332+
{
333+
var response = (byte[])_attestationObject["attStmt"]["response"];
334+
var jwtParts = Encoding.UTF8.GetString(response).Split('.');
335+
var jwtHeaderJSON = JObject.Parse(Encoding.UTF8.GetString(Base64Url.Decode(jwtParts.First())));
336+
jwtHeaderJSON.Remove("x5c");
337+
jwtHeaderJSON.Add("x5c", JToken.FromObject(new List<string> { "RjFEMA=="}));
338+
jwtParts[0] = Base64Url.Encode(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(jwtHeaderJSON)));
339+
response = Encoding.UTF8.GetBytes(string.Join(".", jwtParts));
340+
var attStmt = (CborMap)_attestationObject["attStmt"];
341+
attStmt.Set("response", new CborByteString(response));
342+
var ex = Assert.ThrowsAsync<System.ArgumentException>(() => MakeAttestationResponse());
343+
Assert.Equal("Could not parse X509 certificate.", ex.Result.Message);
344+
}
345+
330346
[Fact]
331347
public void TestAndroidSafetyNetJwtInvalid()
332348
{

Test/Attestation/Apple.cs

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public Apple()
8181
}
8282
}
8383
}
84+
8485
[Fact]
8586
public void TestAppleMissingX5c()
8687
{
@@ -89,6 +90,7 @@ public void TestAppleMissingX5c()
8990
var ex = Assert.ThrowsAsync<Fido2VerificationException>(() => MakeAttestationResponse());
9091
Assert.Equal("Malformed x5c in Apple attestation", ex.Result.Message);
9192
}
93+
9294
[Fact]
9395
public void TestAppleX5cNotArray()
9496
{
@@ -97,6 +99,7 @@ public void TestAppleX5cNotArray()
9799
var ex = Assert.ThrowsAsync<Fido2VerificationException>(() => MakeAttestationResponse());
98100
Assert.Equal("Malformed x5c in Apple attestation", ex.Result.Message);
99101
}
102+
100103
[Fact]
101104
public void TestAppleX5cCountNotOne()
102105
{
@@ -106,6 +109,7 @@ public void TestAppleX5cCountNotOne()
106109
var ex = Assert.ThrowsAsync<Fido2VerificationException>(() => MakeAttestationResponse());
107110
Assert.Equal("Malformed x5c in Apple attestation", ex.Result.Message);
108111
}
112+
109113
[Fact]
110114
public void TestAppleX5cValueNotByteString()
111115
{
@@ -114,6 +118,7 @@ public void TestAppleX5cValueNotByteString()
114118
var ex = Assert.ThrowsAsync<Fido2VerificationException>(() => MakeAttestationResponse());
115119
Assert.Equal("Malformed x5c in Apple attestation", ex.Result.Message);
116120
}
121+
117122
[Fact]
118123
public void TestAppleX5cValueZeroLengthByteString()
119124
{
@@ -123,6 +128,50 @@ public void TestAppleX5cValueZeroLengthByteString()
123128
Assert.Equal("Malformed x5c in Apple attestation", ex.Result.Message);
124129
}
125130

131+
[Fact]
132+
public void TestAppleCertMissingExtension()
133+
{
134+
var invalidX5cStrings = validX5cStrings;
135+
var invalidCert = Convert.FromBase64String(invalidX5cStrings[0]);
136+
invalidCert[424] = 0x42;
137+
invalidX5cStrings[0] = Convert.ToBase64String(invalidCert);
138+
139+
var trustPath = invalidX5cStrings
140+
.Select(x => new X509Certificate2(Convert.FromBase64String(x)))
141+
.ToArray();
142+
143+
var x5c = new CborArray {
144+
trustPath[0].RawData,
145+
trustPath[1].RawData
146+
};
147+
var attStmt = (CborMap)_attestationObject["attStmt"];
148+
attStmt.Set("x5c", x5c);
149+
var ex = Assert.ThrowsAsync<Fido2VerificationException>(() => MakeAttestationResponse());
150+
Assert.Equal("Extension with OID 1.2.840.113635.100.8.2 not found on Apple attestation credCert", ex.Result.Message);
151+
}
152+
153+
[Fact]
154+
public void TestAppleCertCorruptExtension()
155+
{
156+
var invalidX5cStrings = validX5cStrings;
157+
var invalidCert = Convert.FromBase64String(invalidX5cStrings[0]);
158+
invalidCert[429] = 0x03;
159+
invalidX5cStrings[0] = Convert.ToBase64String(invalidCert);
160+
161+
var trustPath = invalidX5cStrings
162+
.Select(x => new X509Certificate2(Convert.FromBase64String(x)))
163+
.ToArray();
164+
165+
var x5c = new CborArray {
166+
trustPath[0].RawData,
167+
trustPath[1].RawData
168+
};
169+
var attStmt = (CborMap)_attestationObject["attStmt"];
170+
attStmt.Set("x5c", x5c);
171+
var ex = Assert.ThrowsAsync<Fido2VerificationException>(() => MakeAttestationResponse());
172+
Assert.Equal("Apple attestation extension has invalid data", ex.Result.Message);
173+
}
174+
126175
[Fact]
127176
public void TestAppleInvalidNonce()
128177
{
@@ -145,27 +194,38 @@ public void TestApplePublicKeyMismatch()
145194
{
146195
var cpkBytes = new byte[] { 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0x79, 0xfe, 0x59, 0x08, 0xbb, 0x51, 0x29, 0xc8, 0x09, 0x38, 0xb7, 0x54, 0xc0, 0x4d, 0x2b, 0x34, 0x0e, 0xfa, 0x66, 0x15, 0xb9, 0x87, 0x69, 0x8b, 0xf5, 0x9d, 0xa4, 0xe5, 0x3e, 0xa3, 0xe6, 0xfe, 0x22, 0x58, 0x20, 0xfb, 0x03, 0xda, 0xa1, 0x27, 0x0d, 0x58, 0x04, 0xe8, 0xab, 0x61, 0xc1, 0x5a, 0xac, 0xa2, 0x43, 0x5c, 0x7d, 0xbf, 0x36, 0x9d, 0x71, 0xca, 0x15, 0xc5, 0x23, 0xb0, 0x00, 0x4a, 0x1b, 0x75, 0xb7 };
147196
_credentialPublicKey = new CredentialPublicKey(cpkBytes);
148-
var trustPath = validX5cStrings
149-
.Select(x => new X509Certificate2(Convert.FromBase64String(x)))
150-
.ToArray();
151-
152-
var X5c = new CborArray {
153-
{ trustPath[0].RawData },
154-
{ trustPath[1].RawData }
155-
};
156-
157-
((CborMap)_attestationObject["attStmt"]).Set("x5c", X5c);
158197

159198
var authData = new AuthenticatorData(_rpIdHash, _flags, _signCount, _acd, _exts).ToByteArray();
160199
_attestationObject.Set("authData", new CborByteString(authData));
161200
var clientData = new
162201
{
163202
type = "webauthn.create",
164203
challenge = _challenge,
165-
origin = "6cc3c9e7967a.ngrok.io",
204+
origin = "https://www.passwordless.dev",
166205
};
167206
var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(clientData);
168207

208+
var data = DataHelper.Concat(authData, SHA256.HashData(clientDataJson));
209+
Span<byte> dataHash = stackalloc byte[32];
210+
SHA256.HashData(data, dataHash);
211+
212+
var invalidX5cStrings = validX5cStrings;
213+
var invalidCert = Convert.FromBase64String(invalidX5cStrings[0]);
214+
Buffer.BlockCopy(dataHash.ToArray(), 0, invalidCert, 433, 32);
215+
invalidCert[485] = 0xdb;
216+
invalidX5cStrings[0] = Convert.ToBase64String(invalidCert);
217+
218+
var trustPath = invalidX5cStrings
219+
.Select(x => new X509Certificate2(Convert.FromBase64String(x)))
220+
.ToArray();
221+
222+
var X5c = new CborArray {
223+
{ trustPath[0].RawData },
224+
{ trustPath[1].RawData }
225+
};
226+
227+
((CborMap)_attestationObject["attStmt"]).Set("x5c", X5c);
228+
169229
var attestationResponse = new AuthenticatorAttestationRawResponse
170230
{
171231
Type = PublicKeyCredentialType.PublicKey,
@@ -193,7 +253,7 @@ public void TestApplePublicKeyMismatch()
193253
{
194254
new PubKeyCredParam(COSE.Algorithm.ES256)
195255
},
196-
Rp = new PublicKeyCredentialRpEntity("6cc3c9e7967a.ngrok.io", "6cc3c9e7967a.ngrok.io", ""),
256+
Rp = new PublicKeyCredentialRpEntity("https://www.passwordless.dev", "6cc3c9e7967a.ngrok.io", ""),
197257
Status = "ok",
198258
User = new Fido2User
199259
{
@@ -213,7 +273,7 @@ public void TestApplePublicKeyMismatch()
213273
{
214274
ServerDomain = "6cc3c9e7967a.ngrok.io",
215275
ServerName = "6cc3c9e7967a.ngrok.io",
216-
Origins = new HashSet<string> { "https://6cc3c9e7967a.ngrok.io" },
276+
Origin = "https://www.passwordless.dev",
217277
});
218278

219279
var credentialMakeResult = lib.MakeNewCredentialAsync(attestationResponse, origChallenge, callback);

Test/Attestation/Packed.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,30 @@ public void TestMissingAlg()
8787
Assert.Equal("Invalid packed attestation algorithm", ex.Result.Message);
8888
}
8989

90+
[Fact]
91+
public void TestEcdaaKeyIdPresent()
92+
{
93+
var (type, alg, crv) = Fido2Tests._validCOSEParameters[0];
94+
var signature = SignData(type, alg, crv);
95+
_attestationObject.Add("attStmt", new CborMap {
96+
{ "alg", alg },
97+
{ "sig", signature },
98+
{ "ecdaaKeyId", signature }
99+
});
100+
var ex = Assert.ThrowsAsync<Fido2VerificationException>(() => MakeAttestationResponse());
101+
Assert.Equal("ECDAA is not yet implemented", ex.Result.Message);
102+
}
103+
104+
[Fact]
105+
public void TestEmptyAttStmt()
106+
{
107+
var (type, alg, crv) = Fido2Tests._validCOSEParameters[0];
108+
var signature = SignData(type, alg, crv);
109+
_attestationObject.Add("attStmt", new CborMap { });
110+
var ex = Assert.ThrowsAsync<Fido2VerificationException>(() => MakeAttestationResponse());
111+
Assert.Equal("Attestation format packed must have attestation statement", ex.Result.Message);
112+
}
113+
90114
[Fact]
91115
public void TestAlgNaN()
92116
{

Test/Attestation/Tpm.cs

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6021,7 +6021,88 @@ public void TestTPMECDAANotSupported()
60216021
}
60226022
}
60236023
}
6024-
6024+
6025+
[Fact]
6026+
public void TestCertInfoNull()
6027+
{
6028+
var ex = Assert.Throws<Fido2VerificationException>(() => new CertInfo(null));
6029+
Assert.Equal("Malformed certInfo bytes", ex.Message);
6030+
}
6031+
6032+
[Fact]
6033+
public void TestCertInfoExtraBytes()
6034+
{
6035+
byte[] certInfo = Convert.FromHexString("ff5443478017000100002097d2ca06ce7dd7fdc56297462cd15f44ba594b0f472557a500659ccea1fcd0a6000000000000000000000000000000000000000000000000000022000b4fb39646c7a88c2322fa048ebaa748ad0c9025c6eca9e53211ffcdd2ee3ea20e000042");
6036+
var ex = Assert.Throws<Fido2VerificationException>(() => new CertInfo(certInfo));
6037+
Assert.Equal("Leftover bits decoding certInfo", ex.Message);
6038+
}
6039+
6040+
[Fact]
6041+
public void TestPubAreaAltKeyedHash()
6042+
{
6043+
using (var rsaAtt = RSA.Create())
6044+
{
6045+
var rsaparams = rsaAtt.ExportParameters(true);
6046+
6047+
unique = rsaparams.Modulus;
6048+
exponent = rsaparams.Exponent;
6049+
6050+
var pubArea = CreatePubArea(
6051+
TpmAlg.TPM_ALG_KEYEDHASH, // Type
6052+
tpmAlg, // Alg
6053+
new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
6054+
new byte[] { 0x00 }, // Policy
6055+
new byte[] { 0x00, 0x10 }, // Symmetric
6056+
new byte[] { 0x00, 0x10 }, // Scheme
6057+
new byte[] { 0x80, 0x00 }, // KeyBits
6058+
exponent, // Exponent
6059+
curveId, // CurveID
6060+
kdf, // KDF
6061+
unique // Unique
6062+
);
6063+
6064+
var ex = Assert.Throws<Fido2VerificationException>(() => new PubArea(pubArea));
6065+
Assert.Equal("TPM_ALG_KEYEDHASH not yet supported", ex.Message);
6066+
}
6067+
}
6068+
6069+
[Fact]
6070+
public void TestPubAreaAltSymCipher()
6071+
{
6072+
using (var rsaAtt = RSA.Create())
6073+
{
6074+
var rsaparams = rsaAtt.ExportParameters(true);
6075+
6076+
unique = rsaparams.Modulus;
6077+
exponent = rsaparams.Exponent;
6078+
6079+
var pubArea = CreatePubArea(
6080+
TpmAlg.TPM_ALG_SYMCIPHER, // Type
6081+
tpmAlg, // Alg
6082+
new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
6083+
new byte[] { 0x00 }, // Policy
6084+
new byte[] { 0x00, 0x10 }, // Symmetric
6085+
new byte[] { 0x00, 0x10 }, // Scheme
6086+
new byte[] { 0x80, 0x00 }, // KeyBits
6087+
exponent, // Exponent
6088+
curveId, // CurveID
6089+
kdf, // KDF
6090+
unique // Unique
6091+
);
6092+
6093+
var ex = Assert.Throws<Fido2VerificationException>(() => new PubArea(pubArea));
6094+
Assert.Equal("TPM_ALG_SYMCIPHER not yet supported", ex.Message);
6095+
}
6096+
}
6097+
6098+
[Fact]
6099+
public void TestPubAreaExtraBytes()
6100+
{
6101+
var pubArea = Convert.FromHexString("0001000000000000000100001000108000010001000100b181b7dac685f3df1b0a24042b6e03f55a1483499701e5d6906dc5d4bdcce496e76268ec77eeef950e4638e53c61af0230cbcaa2ea6c5d1ed640f72854765e7fbab7206242ca8ced985b4fa19be29f69abd6f73248ee0fe9c8ee427799a1b745e32211099a8a087fb636da59fb3b5e34c0d610b6342c6086c06dad0bb71439c257b99c09593ff4ab8a4046e634920f04e2297b9aa9c6ae759035af5840e497112c3949077ec7879c2108d751e9220eff6cd974db209c91489d337208775018a1a402301137f724f21ec5a239f708fd4514582bae96047c0544c7da48cb1c876cf37c1dcc6509fa22976e176a68d6f2afe67efe18e9fe8a4d891cd167eba2da0542");
6102+
var ex = Assert.Throws<Fido2VerificationException>(() => new PubArea(pubArea));
6103+
Assert.Equal("Leftover bytes decoding pubArea", ex.Message);
6104+
}
6105+
60256106
internal static byte[] CreatePubArea(
60266107
TpmAlg type,
60276108
ReadOnlySpan<byte> alg,
@@ -6037,7 +6118,7 @@ internal static byte[] CreatePubArea(
60376118
{
60386119
var raw = new MemoryStream();
60396120

6040-
if (type is TpmAlg.TPM_ALG_RSA)
6121+
if (type is TpmAlg.TPM_ALG_ECC)
60416122
{
60426123
raw.Write(type.ToUInt16BigEndianBytes());
60436124
raw.Write(alg);
@@ -6046,12 +6127,12 @@ internal static byte[] CreatePubArea(
60466127
raw.Write(policy);
60476128
raw.Write(symmetric);
60486129
raw.Write(scheme);
6049-
raw.Write(keyBits);
6050-
raw.Write(BitConverter.GetBytes(exponent[0] + (exponent[1] << 8) + (exponent[2] << 16)));
6130+
raw.Write(curveID);
6131+
raw.Write(kdf);
60516132
raw.Write(GetUInt16BigEndianBytes(unique.Length));
6052-
raw.Write(unique); ;
6133+
raw.Write(unique);
60536134
}
6054-
else if (type is TpmAlg.TPM_ALG_ECC)
6135+
else
60556136
{
60566137
raw.Write(type.ToUInt16BigEndianBytes());
60576138
raw.Write(alg);
@@ -6060,8 +6141,8 @@ internal static byte[] CreatePubArea(
60606141
raw.Write(policy);
60616142
raw.Write(symmetric);
60626143
raw.Write(scheme);
6063-
raw.Write(curveID);
6064-
raw.Write(kdf);
6144+
raw.Write(keyBits);
6145+
raw.Write(BitConverter.GetBytes(exponent[0] + (exponent[1] << 8) + (exponent[2] << 16)));
60656146
raw.Write(GetUInt16BigEndianBytes(unique.Length));
60666147
raw.Write(unique);
60676148
}

Test/AuthenticatorDataTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
using Fido2NetLib;
3+
using Fido2NetLib.Objects;
4+
using Xunit;
5+
6+
namespace Test
7+
{
8+
public class AuthenticatorDataTests
9+
{
10+
[Fact]
11+
public void AuthenticatorDataNull()
12+
{
13+
byte[] ad = null;
14+
var ex = Assert.Throws<Fido2VerificationException>(() => new AuthenticatorData(ad));
15+
Assert.Equal("Authenticator data cannot be null", ex.Message);
16+
}
17+
18+
[Fact]
19+
public void AuthenticatorDataMinLen()
20+
{
21+
byte[] ad = new byte[36];
22+
var ex = Assert.Throws<Fido2VerificationException>(() => new AuthenticatorData(ad));
23+
Assert.Equal("Authenticator data is less than the minimum structure length of 37", ex.Message);
24+
}
25+
26+
[Fact]
27+
public void AuthenticatorDataExtraBytes()
28+
{
29+
byte[] ad = Convert.FromHexString("49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97634100000000000000000000000000000000000000000040ee726cb6daf874b4ac9ab5a76870777aca49d2e6bdaec276c2cfbddc115c34e3fae20fecff8c0b143be496b358720d0108de9d7548c92f10df0d206b78a40b03a501020326200121582050e028e71aac2683df256b14e7487b7364bbfe594fd0ac0623abc99048f5378f225820364cc49e05f849f381f23104208c9bc1880e899c7034721c52966b99793f578242");
30+
var ex = Assert.Throws<Fido2VerificationException>(() => new AuthenticatorData(ad));
31+
Assert.Equal("Leftover bytes decoding AuthenticatorData", ex.Message);
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)