This repository has been archived by the owner on Aug 7, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 133
/
Copy pathOfflineVerify.cs
180 lines (170 loc) · 6.54 KB
/
OfflineVerify.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
namespace SafetyNetCheck
{
/// <summary>
/// Sample code to verify the device attestation offline.
/// </summary>
public static class OfflineVerify
{
/// <summary>
/// Parses and verifies a SafetyNet attestation statement.
/// </summary>
/// <param name="signedAttestationStatement">A string containing the
/// JWT attestation statement.</param>
/// <returns>A parsed attestation statement. null if the statement is
/// invalid.</returns>
public static AttestationStatement ParseAndVerify(
string signedAttestationStatement)
{
// First parse the token and get the embedded keys.
JwtSecurityToken token;
try
{
token = new JwtSecurityToken(signedAttestationStatement);
}
catch (ArgumentException)
{
// The token is not in a valid JWS format.
return null;
}
// We just want to validate the authenticity of the certificate.
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = GetEmbeddedKeys(token)
};
// Perform the validation
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken validatedToken;
try
{
tokenHandler.ValidateToken(
signedAttestationStatement,
validationParameters,
out validatedToken);
}
catch (ArgumentException)
{
// Signature validation failed.
return null;
}
// Verify the hostname
if (!(validatedToken.SigningKey is X509SecurityKey))
{
// The signing key is invalid.
return null;
}
if (!VerifyHostname(
"attest.android.com",
validatedToken.SigningKey as X509SecurityKey))
{
// The certificate isn't issued for the hostname
// attest.android.com.
return null;
}
// Parse and use the data JSON.
Dictionary<string, string> claimsDictionary = token.Claims
.ToDictionary(x => x.Type, x => x.Value);
return new AttestationStatement(claimsDictionary);
}
/// <summary>
/// Verifes an X509Security key, and checks that it is issued for a
/// given hostname.
/// </summary>
/// <param name="hostname">The hostname to check to.</param>
/// <param name="securityKey">The security key to verify.</param>
/// <returns>true if securityKey is valid and is issued to the given
/// hostname.</returns>
private static bool VerifyHostname(
string hostname,
X509SecurityKey securityKey)
{
string commonName;
try
{
// Verify the certificate with Verify(). Alternatively, you
// could use the commented code below instead of Verify(), to
// get more details of why a particular verification failed.
//
// var chain = new X509Chain();
// var chainBuilt = chain.Build(securityKey.Certificate);
// if (!chainBuilt)
// {
// string s; // One could use a StringBuilder instead.
// foreach (X509ChainStatus chainStatus in
// chain.ChainStatus)
// {
// s += string.Format(
// "Chain error: {0} {1}\n",
// chainStatus.Status,
// chainStatus.StatusInformation);
// }
// }
if (!securityKey.Certificate.Verify())
{
return false;
}
commonName = securityKey.Certificate.GetNameInfo(
X509NameType.DnsName, false);
}
catch (CryptographicException)
{
return false;
}
return (commonName == hostname);
}
/// <summary>
/// Retrieves the X509 security keys embedded in a JwtSecurityToken.
/// </summary>
/// <param name="token">The token where the keys are to be retrieved
/// from.</param>
/// <returns>The embedded security keys. null if there are no keys in
/// the security token.</returns>
/// <exception cref="KeyNotFoundException">Thrown when the JWT data
/// does not contain a valid signature
/// header "x5c".</exception>
/// <exception cref="CryptographicException">Thrwon when the JWT data
/// does not contain valid signing
/// keys.</exception>
private static X509SecurityKey[] GetEmbeddedKeys(
JwtSecurityToken token)
{
// The certificates are embedded in the "x5c" part of the header.
// We extract them, parse them, and then create X509 keys out of
// them.
X509SecurityKey[] keys = null;
keys = (token.Header["x5c"] as JArray)
.Values<string>()
.Select(x => new X509SecurityKey(
new X509Certificate2(Convert.FromBase64String(x))))
.ToArray();
return keys;
}
}
}