/// <summary>
        /// Build an instance of <see cref="NodeEntitlements"/> from the information supplied on the
        /// command line by the user
        /// </summary>
        /// <returns>Either a usable (and completely valid) <see cref="NodeEntitlements"/> or a set
        /// of errors.</returns>
        private Errorable <NodeEntitlements> Build()
        {
            var entitlement = new NodeEntitlements();
            var errors      = new List <string>();

            ConfigureOptional(VirtualMachineId, url => entitlement.WithVirtualMachineId(url));
            Configure(NotBefore, notBefore => entitlement.FromInstant(notBefore));
            Configure(NotAfter, notAfter => entitlement.UntilInstant(notAfter));
            Configure(Audience, audience => entitlement.WithAudience(audience));
            Configure(Issuer, issuer => entitlement.WithIssuer(issuer));
            ConfigureAll(Addresses, address => entitlement.AddIpAddress(address));
            ConfigureAll(Applications, app => entitlement.AddApplication(app));

            if (errors.Any())
            {
                return(Errorable.Failure <NodeEntitlements>(errors));
            }

            return(Errorable.Success(entitlement));

            // <param name="readConfiguration">function to read the configuration value.</param>
            // <param name="applyConfiguration">function to modify our configuration with the value read.</param>
            void Configure <V>(Func <Errorable <V> > readConfiguration, Func <V, NodeEntitlements> applyConfiguration)
            {
                readConfiguration().Match(
                    whenSuccessful: value => entitlement = applyConfiguration(value),
                    whenFailure: e => errors.AddRange(e));
            }

            // <param name="readConfiguration">function to read the configuration value.</param>
            // <param name="applyConfiguration">function to modify our configuration with the value read.</param>
            void ConfigureOptional <V>(Func <Errorable <V> > readConfiguration, Func <V, NodeEntitlements> applyConfiguration)
                where V : class
Exemple #2
0
        private List <Claim> CreateClaims(NodeEntitlements entitlements)
        {
            var claims = new List <Claim>();

            if (!string.IsNullOrEmpty(entitlements.VirtualMachineId))
            {
                _logger.LogDebug("Virtual machine Id: {VirtualMachineId}", entitlements.VirtualMachineId);
                claims.Add(new Claim(Claims.VirtualMachineId, entitlements.VirtualMachineId));
            }

            if (!string.IsNullOrEmpty(entitlements.Identifier))
            {
                _logger.LogDebug("Entitlement Id: {EntitlementId}", entitlements.Identifier);
                claims.Add(new Claim(Claims.EntitlementId, entitlements.Identifier));
            }

            foreach (var ip in entitlements.IpAddresses)
            {
                _logger.LogDebug("IP Address: {IP}", ip);
                claims.Add(new Claim(Claims.IpAddress, ip.ToString()));
            }

            foreach (var app in entitlements.Applications)
            {
                _logger.LogDebug("Application Id: {ApplicationId}", app);
                var claim = new Claim(Claims.Application, app);
                claims.Add(claim);
            }

            return(claims);
        }
Exemple #3
0
        /// <summary>
        /// Generate a token
        /// </summary>
        /// <param name="entitlements">Details of the entitlements to encode into the token.</param>
        /// <param name="signingCert">Certificate to use when signing the token (optional).</param>
        /// <param name="encryptionCert">Certificate to use when encrypting the token (optional).</param>
        /// <returns>Generated token, if any; otherwise all related errors.</returns>
        private static string GenerateToken(
            NodeEntitlements entitlements,
            X509Certificate2 signingCert    = null,
            X509Certificate2 encryptionCert = null)
        {
            SigningCredentials signingCredentials = null;

            if (signingCert != null)
            {
                var signingKey = new X509SecurityKey(signingCert);
                signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.RsaSha512Signature);
            }

            EncryptingCredentials encryptingCredentials = null;

            if (encryptionCert != null)
            {
                var encryptionKey = new X509SecurityKey(encryptionCert);
                encryptingCredentials = new EncryptingCredentials(
                    encryptionKey, SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes256CbcHmacSha512);
            }

            var entitlementWithIdentifier = entitlements.WithIdentifier($"entitlement-{Guid.NewGuid():D}");

            var generator = new TokenGenerator(_logger, signingCredentials, encryptingCredentials);

            return(generator.Generate(entitlementWithIdentifier));
        }
Exemple #4
0
        /// <summary>
        /// Generate a token from a software entitlement
        /// </summary>
        /// <param name="entitlements">Software entitlements to use when populating the token.</param>
        /// <returns>The generated token.</returns>
        public string Generate(NodeEntitlements entitlements)
        {
            if (entitlements == null)
            {
                throw new ArgumentNullException(nameof(entitlements));
            }

            _logger.LogDebug(
                "Not Before: {NotBefore}",
                entitlements.NotBefore.ToString(TimestampParser.ExpectedFormat));
            _logger.LogDebug(
                "Not After: {NotAfter}",
                entitlements.NotAfter.ToString(TimestampParser.ExpectedFormat));

            if (SigningCredentials != null)
            {
                _logger.LogDebug(
                    "Signing with {Credentials}",
                    SigningCredentials.Kid);
            }

            if (EncryptingCredentials != null)
            {
                _logger.LogDebug(
                    "Encrypting with {Algorithm}/{Credentials}",
                    EncryptingCredentials.Alg,
                    EncryptingCredentials.Key.KeyId);
            }

            var effectiveIssuer = entitlements.Issuer ?? Claims.DefaultIssuer;

            _logger.LogDebug("Issued by {Issuer}", effectiveIssuer);

            var effectiveAudience = entitlements.Audience ?? Claims.DefaultAudience;

            _logger.LogDebug("Audience is {Audience}", effectiveAudience);

            var claims                  = CreateClaims(entitlements);
            var claimsIdentity          = new ClaimsIdentity(claims);
            var securityTokenDescriptor = new SecurityTokenDescriptor
            {
                Subject               = claimsIdentity,
                NotBefore             = entitlements.NotBefore.UtcDateTime,
                Expires               = entitlements.NotAfter.UtcDateTime,
                IssuedAt              = DateTimeOffset.Now.UtcDateTime,
                Issuer                = effectiveIssuer,
                Audience              = effectiveAudience,
                SigningCredentials    = SigningCredentials,
                EncryptingCredentials = EncryptingCredentials
            };

            var handler = new JwtSecurityTokenHandler();
            var token   = handler.CreateToken(securityTokenDescriptor);

            _logger.LogDebug("Raw token: {Token}", token);

            return(handler.WriteToken(token));
        }
Exemple #5
0
        /// <summary>
        /// Cloning constructor to initialize a new instance of the <see cref="NodeEntitlements"/>
        /// class as a (near) copy of an existing one.
        /// </summary>
        /// <remarks>Specify any of the optional parameters to modify the clone from the original.</remarks>
        /// <param name="original">Original entitlement to clone.</param>
        /// <param name="notBefore">Optionally specify a new value for <see cref="NotBefore"/>.</param>
        /// <param name="notAfter">Optionally specify a new value for <see cref="NotAfter"/>.</param>
        /// <param name="virtualMachineId">Optionally specify a new value for <see cref="VirtualMachineId"/>.</param>
        /// <param name="applications">The set of applications entitled to run.</param>
        /// <param name="identifier">Identifier to use for this entitlement.</param>
        /// <param name="addresses">Addresses of the entitled machine.</param>
        /// <param name="audience">Audience for whom the token is intended.</param>
        /// <param name="issuer">Issuer identifier for the token.</param>
        private NodeEntitlements(
            NodeEntitlements original,
            DateTimeOffset?notBefore = null,
            DateTimeOffset?notAfter  = null,
            string virtualMachineId  = null,
            ImmutableHashSet <string> applications = null,
            string identifier = null,
            ImmutableHashSet <IPAddress> addresses = null,
            string audience = null,
            string issuer   = null)
        {
            Created = original.Created;

            NotBefore        = notBefore ?? original.NotBefore;
            NotAfter         = notAfter ?? original.NotAfter;
            VirtualMachineId = virtualMachineId ?? original.VirtualMachineId;
            Applications     = applications ?? original.Applications;
            Identifier       = identifier ?? original.Identifier;
            IpAddresses      = addresses ?? original.IpAddresses;
            Audience         = audience ?? original.Audience;
            Issuer           = issuer ?? original.Issuer;
        }
Exemple #6
0
        /// <summary>
        /// Verify the provided software entitlement token
        /// </summary>
        /// <param name="tokenString">A software entitlement token to verify.</param>
        /// <param name="expectedAudience">The audience for whom the token should be intended.</param>
        /// <param name="expectedIssuer">The issuer who should have created the token.</param>
        /// <param name="application">The specific application id of the application </param>
        /// <param name="ipAddress">Address of the machine requesting token validation.</param>
        /// <returns>Either a software entitlement describing the approved entitlement, or errors
        /// explaining why it wasn't approved.</returns>
        public Errorable <NodeEntitlements> Verify(
            string tokenString,
            string expectedAudience,
            string expectedIssuer,
            string application,
            IPAddress ipAddress)
        {
            var validationParameters = new TokenValidationParameters
            {
                ValidateAudience         = true,
                ValidAudience            = expectedAudience,
                ValidateIssuer           = true,
                ValidIssuer              = expectedIssuer,
                ValidateLifetime         = true,
                RequireExpirationTime    = true,
                RequireSignedTokens      = SigningKey != null,
                ClockSkew                = TimeSpan.FromSeconds(60),
                IssuerSigningKey         = SigningKey,
                ValidateIssuerSigningKey = true,
                TokenDecryptionKey       = EncryptionKey
            };

            try
            {
                var handler   = new JwtSecurityTokenHandler();
                var principal = handler.ValidateToken(tokenString, validationParameters, out var token);

                if (!VerifyApplication(principal, application))
                {
                    return(ApplicationNotEntitledError(application));
                }

                if (!VerifyIpAddress(principal, ipAddress))
                {
                    return(MachineNotEntitledError(ipAddress));
                }

                var entitlementIdClaim = principal.FindFirst(Claims.EntitlementId);
                if (entitlementIdClaim == null)
                {
                    return(IdentifierNotPresentError());
                }

                var result = new NodeEntitlements()
                             .FromInstant(new DateTimeOffset(token.ValidFrom))
                             .UntilInstant(new DateTimeOffset(token.ValidTo))
                             .AddApplication(application)
                             .WithIdentifier(entitlementIdClaim.Value)
                             .AddIpAddress(ipAddress);

                var virtualMachineIdClaim = principal.FindFirst(Claims.VirtualMachineId);
                if (virtualMachineIdClaim != null)
                {
                    result = result.WithVirtualMachineId(virtualMachineIdClaim.Value);
                }

                return(Errorable.Success(result));
            }
            catch (SecurityTokenNotYetValidException exception)
            {
                return(TokenNotYetValidError(exception.NotBefore));
            }
            catch (SecurityTokenExpiredException exception)
            {
                return(TokenExpiredError(exception.Expires));
            }
            catch (SecurityTokenException exception)
            {
                return(InvalidTokenError(exception.Message));
            }
        }