Reputation: 67
I'm trying to verify a token sent from the client side Firebase API on a C# Server. I've tried almost everything but for some reason I can't get the token verification method correct. I have implemented everything up to getting the public keys from the network, but I can't seem to figure out to get the RS256 algorithim to equal the JWT's 3rd chunk.
class FirebaseJWTAuth {
public string FirebaseId;
private HttpClient Req;
//initialize all the settings
public FirebaseJWTAuth(string firebaseId) {
firebaseId = FirebaseId;
Req = new HttpClient();
Req.BaseAddress = new Uri("https://www.googleapis.com/robot/v1/metadata/");
}
//given a token, return the user id as a string if valid, null if invalid
public async Task<string> Verify(string token) {
//following instructions from https://firebase.google.com/docs/auth/admin/verify-id-tokens
string hashChunk = token; //keep for hashing later on
hashChunk = hashChunk.Substring(0, hashChunk.LastIndexOf('.'));
token = token.Replace('-', '+').Replace('_', '/'); //sanitize for base64 on C#
string[] sections = token.Split('.'); //split into 3 sections according to JWT standards
JwtHeader header = B64Json<JwtHeader>(sections[0]);
//verify the header
if(header.alg != "RS256") {
return null;
}
//get the public keys
HttpResponseMessage res = await Req.GetAsync("x509/[email protected]"); //make async
string keyDictStr = await res.Content.ReadAsStringAsync();
Dictionary<string, string> keyDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(keyDictStr);
string keyStr = null;
keyDict.TryGetValue(header.kid, out keyStr);
if(keyStr == null) {
return null;
}
//Use the RSACryptoServiceProvider to verify the hash
var rsaCrypto = CertFromPem(keyStr);
byte[] plainText = Encoding.UTF8.GetBytes(hashChunk);
byte[] hashed = SHA256Managed.Create().ComputeHash(plainText);
byte[] encrypted = rsaCrypto.Encrypt(hashed, false);
byte[] challenge = SafeB64Decode(sections[2]);
Console.WriteLine(encrypted.SequenceEqual(challenge)); //QUESTION IN THE ISSUE: expect to be true, but always false
//for debugging purposes
Console.WriteLine(Convert.ToBase64String(challenge));
Console.WriteLine(Convert.ToBase64String(encrypted));
return "didn't really get down to this part";
}
//given a string, return the RSACryptoServiceProvider which corresponds to the public key
static RSACryptoServiceProvider CertFromPem(string pemKey) {
X509Certificate2 cert = new X509Certificate2();
cert.Import(Encoding.UTF8.GetBytes(pemKey));
Console.WriteLine(cert.ToString());
return (RSACryptoServiceProvider) cert.PublicKey.Key;
}
//b64 decoding with padding to calm the C# converter
static byte[] SafeB64Decode(string encoded) {
string encodedPad = encoded + new string('=', encoded.Length % 4);
return Convert.FromBase64String(encodedPad);
}
static string SafeB64DecodeStr(string encoded) {
return Encoding.UTF8.GetString(SafeB64Decode(encoded));
}
static T B64Json<T> (string encoded) {
string decoded = SafeB64DecodeStr(encoded);
Console.WriteLine(decoded);
return JsonConvert.DeserializeObject<T>(decoded);
}
//structs representing the first 2 chunks of a JWT
private struct JwtHeader {
public string alg;
public string kid;
}
private struct JwtPayload {
public long exp;
public long iat;
public string aud;
public string iss;
public string sub;
}
}
I know this is rough (there's no verification of the payload) but I can't match the tokens. I'm just a junior developer but I've tried to add extra comments and spacing to make your reading easier.
I've been testing this code on my personal token which I am not uploading here for obvious reasons. However, if you want to test your code go on to the firebase-auth demo site and login. Then, open up a DevTools console and type firebase.auth().currentUser.getIdToken(true).then(console.log)
. Your token will pop up in the console.
Thanks in advance.
Upvotes: 0
Views: 1023
Reputation: 67
/facepalm
I forgot that RSA Signing != RSA Encrypting and apparently C# has different libraries for both. The magic class was RSAPKCS1SignatureDeformatter
.
The answer is to use the RSAPKCS1SignatureDeformatter.VerifySignature
method instead of the encryption-based RSACryptoServiceProvider
Upvotes: 2