private async Task VerifySignatureAsync( string[] segments, string keyId, CancellationToken cancellationToken) { byte[] hash; using (var hashAlg = SHA256.Create()) { hash = hashAlg.ComputeHash( Encoding.ASCII.GetBytes($"{segments[0]}.{segments[1]}")); } var signature = JwtUtils.Base64DecodeToBytes(segments[2]); var keys = await this.keySource.GetPublicKeysAsync(cancellationToken) .ConfigureAwait(false); var verified = keys.Any(key => #if NETSTANDARD1_5 || NETSTANDARD2_0 key.Id == keyId && key.RSA.VerifyHash( hash, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1) #elif NET45 key.Id == keyId && ((RSACryptoServiceProvider)key.RSA).VerifyHash(hash, Sha256Oid, signature) #else #error Unsupported target #endif ); if (!verified) { throw this.CreateException($"Failed to verify {this.shortName} signature."); } }
private async Task VerifySignatureAsync( string[] segments, string keyId, CancellationToken cancellationToken) { if (this.IsEmulatorMode) { cancellationToken.ThrowIfCancellationRequested(); return; } byte[] hash; using (var hashAlg = SHA256.Create()) { hash = hashAlg.ComputeHash( Encoding.ASCII.GetBytes($"{segments[0]}.{segments[1]}")); } var signature = JwtUtils.Base64DecodeToBytes(segments[2]); var keys = await this.keySource.GetPublicKeysAsync(cancellationToken) .ConfigureAwait(false); var verified = keys.Any(key => key.Id == keyId && key.RSA.VerifyHash( hash, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1) ); if (!verified) { throw this.CreateException($"Failed to verify {this.shortName} signature."); } }
internal async Task <string> CreateCustomTokenAsync( string uid, IDictionary <string, object> developerClaims = null, CancellationToken cancellationToken = default(CancellationToken)) { if (string.IsNullOrEmpty(uid)) { throw new ArgumentException("uid must not be null or empty"); } else if (uid.Length > 128) { throw new ArgumentException("uid must not be longer than 128 characters"); } if (developerClaims != null) { foreach (var entry in developerClaims) { if (ReservedClaims.Contains(entry.Key)) { throw new ArgumentException( $"reserved claim {entry.Key} not allowed in developerClaims"); } } } var header = new JsonWebSignature.Header() { Algorithm = "RS256", Type = "JWT", }; var issued = (int)(this.clock.UtcNow - UnixEpoch).TotalSeconds; var keyId = await this.Signer.GetKeyIdAsync(cancellationToken).ConfigureAwait(false); var payload = new CustomTokenPayload() { Uid = uid, Issuer = keyId, Subject = keyId, Audience = FirebaseAudience, IssuedAtTimeSeconds = issued, ExpirationTimeSeconds = issued + TokenDurationSeconds, TenantId = this.TenantId, }; if (developerClaims != null && developerClaims.Count > 0) { payload.Claims = developerClaims; } return(await JwtUtils.CreateSignedJwtAsync( header, payload, this.Signer, cancellationToken).ConfigureAwait(false)); }
internal async Task <FirebaseToken> VerifyTokenAsync( string token, CancellationToken cancellationToken = default(CancellationToken)) { if (string.IsNullOrEmpty(token)) { throw new ArgumentException($"{this.shortName} must not be null or empty."); } string[] segments = token.Split('.'); if (segments.Length != 3) { throw this.CreateException($"Incorrect number of segments in {this.shortName}."); } var header = JwtUtils.Decode <JsonWebSignature.Header>(segments[0]); var payload = JwtUtils.Decode <FirebaseToken.Args>(segments[1]); var projectIdMessage = $"Make sure the {this.shortName} comes from the same Firebase " + "project as the credential used to initialize this SDK."; var verifyTokenMessage = $"See {this.url} for details on how to retrieve a value " + $"{this.shortName}."; var issuer = this.issuer + this.ProjectId; string error = null; var errorCode = this.invalidTokenCode; var currentTimeInSeconds = this.clock.UnixTimestamp(); if (string.IsNullOrEmpty(header.KeyId)) { if (payload.Audience == FirebaseAudience) { error = $"{this.operation} expects {this.articledShortName}, but was given a custom " + "token."; } else if (header.Algorithm == "HS256") { error = $"{this.operation} expects {this.articledShortName}, but was given a legacy " + "custom token."; } else { error = $"Firebase {this.shortName} has no 'kid' claim."; } } else if (header.Algorithm != "RS256") { error = $"Firebase {this.shortName} has incorrect algorithm. Expected RS256 but got " + $"{header.Algorithm}. {verifyTokenMessage}"; } else if (this.ProjectId != payload.Audience) { error = $"Firebase {this.shortName} has incorrect audience (aud) claim. Expected " + $"{this.ProjectId} but got {payload.Audience}. {projectIdMessage} " + $"{verifyTokenMessage}"; } else if (payload.Issuer != issuer) { error = $"Firebase {this.shortName} has incorrect issuer (iss) claim. Expected " + $"{issuer} but got {payload.Issuer}. {projectIdMessage} {verifyTokenMessage}"; } else if (payload.IssuedAtTimeSeconds - ClockSkewSeconds > currentTimeInSeconds) { error = $"Firebase {this.shortName} issued at future timestamp " + $"{payload.IssuedAtTimeSeconds}. Expected to be less than " + $"{currentTimeInSeconds}."; } else if (payload.ExpirationTimeSeconds + ClockSkewSeconds < currentTimeInSeconds) { error = $"Firebase {this.shortName} expired at {payload.ExpirationTimeSeconds}. " + $"Expected to be greater than {currentTimeInSeconds}."; errorCode = this.expiredIdTokenCode; } else if (string.IsNullOrEmpty(payload.Subject)) { error = $"Firebase {this.shortName} has no or empty subject (sub) claim."; } else if (payload.Subject.Length > 128) { error = $"Firebase {this.shortName} has a subject claim longer than 128 characters."; } else if (this.TenantId != null && this.TenantId != payload.Firebase?.Tenant) { error = $"Firebase {this.shortName} has incorrect tenant ID. Expected " + $"{this.TenantId} but got {payload.Firebase?.Tenant}"; errorCode = AuthErrorCode.TenantIdMismatch; } if (error != null) { throw this.CreateException(error, errorCode); } await this.VerifySignatureAsync(segments, header.KeyId, cancellationToken) .ConfigureAwait(false); var allClaims = JwtUtils.Decode <Dictionary <string, object> >(segments[1]); // Remove standard claims, so that only custom claims would remain. foreach (var claim in StandardClaims) { allClaims.Remove(claim); } payload.Claims = allClaims.ToImmutableDictionary(); return(new FirebaseToken(payload)); }