Exemple #1
0
        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));
        }
Exemple #4
0
        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));
        }