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.");
            }
        }
Beispiel #2
0
        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;
            }
Beispiel #4
0
        /// <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.");
            }
        }
Beispiel #5
0
        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));
        }