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."); } }
public 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, }; if (developerClaims != null && developerClaims.Count > 0) { payload.Claims = developerClaims; } return(await JwtUtils.CreateSignedJwtAsync( header, payload, this.signer, cancellationToken).ConfigureAwait(false)); }
internal Request(ImportUserRecordArgs args) { this.Uid = UserRecordArgs.CheckUid(args.Uid, true); this.Email = UserRecordArgs.CheckEmail(args.Email); this.PhotoUrl = UserRecordArgs.CheckPhotoUrl(args.PhotoUrl); this.PhoneNumber = UserRecordArgs.CheckPhoneNumber(args.PhoneNumber); if (!string.IsNullOrEmpty(args.DisplayName)) { this.DisplayName = args.DisplayName; } if (args.UserMetadata != null) { this.CreatedAt = args.UserMetadata.CreationTimestamp; this.LastLoginAt = args.UserMetadata.LastSignInTimestamp; } if (args.PasswordHash != null) { this.PasswordHash = JwtUtils.UrlSafeBase64Encode(args.PasswordHash); } if (args.PasswordSalt != null) { this.PasswordSalt = JwtUtils.UrlSafeBase64Encode(args.PasswordSalt); } if (args.UserProviders != null && args.UserProviders.Count() > 0) { this.ProviderUserInfo = new List <UserProvider.Request>( args.UserProviders.Select(userProvider => userProvider.ToRequest())); } if (args.CustomClaims != null && args.CustomClaims.Count > 0) { var serialized = UserRecordArgs.CheckCustomClaims(args.CustomClaims); this.CustomAttributes = serialized; } this.EmailVerified = args.EmailVerified; this.Disabled = args.Disabled; }
/// <summary> /// Verifies the integrity of a JWT by validating its signature. The JWT must be specified /// as an array of three segments (header, body and signature). /// </summary> 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 _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 new FirebaseException($"Failed to verify {_shortName} signature."); } }
public async Task <FirebaseToken> VerifyTokenAsync( string token, CancellationToken cancellationToken = default(CancellationToken)) { if (string.IsNullOrEmpty(token)) { throw new ArgumentException($"{_shortName} must not be null or empty."); } string[] segments = token.Split('.'); if (segments.Length != 3) { throw new FirebaseException($"Incorrect number of segments in ${_shortName}."); } var header = JwtUtils.Decode <JsonWebSignature.Header>(segments[0]); var payload = JwtUtils.Decode <FirebaseTokenArgs>(segments[1]); var projectIdMessage = $"Make sure the {_shortName} comes from the same Firebase " + "project as the credential used to initialize this SDK."; var verifyTokenMessage = $"See {_url} for details on how to retrieve a value " + $"{_shortName}."; var issuer = _issuer + ProjectId; string error = null; if (string.IsNullOrEmpty(header.KeyId)) { if (FirebaseAudience == payload.Audience) { error = $"{_operation} expects {_articledShortName}, but was given a custom " + "token."; } else if (header.Algorithm == "HS256") { error = $"{_operation} expects {_articledShortName}, but was given a legacy " + "custom token."; } else { error = $"Firebase {_shortName} has no 'kid' claim."; } } else if (header.Algorithm != "RS256") { error = $"Firebase {_shortName} has incorrect algorithm. Expected RS256 but got " + $"{header.Algorithm}. {verifyTokenMessage}"; } else if (ProjectId != payload.Audience) { error = $"{_shortName} has incorrect audience (aud) claim. Expected {ProjectId} " + $"but got {payload.Audience}. {projectIdMessage} {verifyTokenMessage}"; } else if (payload.Issuer != issuer) { error = $"{_shortName} has incorrect issuer (iss) claim. Expected {issuer} but " + $"got {payload.Issuer}. {projectIdMessage} {verifyTokenMessage}"; } else if (payload.IssuedAtTimeSeconds > _clock.UnixTimestamp()) { error = $"Firebase {_shortName} issued at future timestamp"; } else if (payload.ExpirationTimeSeconds < _clock.UnixTimestamp()) { error = $"Firebase {_shortName} expired at {payload.ExpirationTimeSeconds}"; } else if (string.IsNullOrEmpty(payload.Subject)) { error = $"Firebase {_shortName} has no or empty subject (sub) claim."; } else if (payload.Subject.Length > 128) { error = $"Firebase {_shortName} has a subject claim longer than 128 characters."; } if (error != null) { throw new FirebaseException(error); } await 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)); }