public async Task RetrieveAccessToken_InvalidCertificate_ReturnsUnauthorized()
        {
            var options = new OioIdwsAuthorizationServiceOptions
            {
                AccessTokenIssuerPath            = new PathString("/accesstoken/issue"),
                AccessTokenRetrievalPath         = new PathString("/accesstoken"),
                IssuerAudiences                  = () => Task.FromResult(new IssuerAudiences[0]),
                TrustedWspCertificateThumbprints = new[] { "other cert" },
                CertificateValidator             = X509CertificateValidator.None //no reason for tests to validate certs
            };

            X509Certificate2 certificate = null;

            using (var server = TestServerWithClientCertificate.Create(() => certificate, app =>
            {
                app.UseOioIdwsAuthorizationService(options);
            }))
            {
                server.BaseAddress = new Uri("https://localhost/");

                //without presenting certificate
                var response = await server.HttpClient.GetAsync($"/accesstoken?accesstoken1");

                Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode);

                //with untrusted certificate
                certificate = CertificateUtil.GetCertificate("d9f10c97aa647727adb64a349bb037c5c23c9a7a");
                response    = await server.HttpClient.GetAsync($"/accesstoken?accesstoken1");

                Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode);
            }
        }
示例#2
0
        async Task PerformValidationTestAsync(
            X509Certificate2 clientCertificate,
            IEnumerable <IssuerAudiences> issuerAudiences,
            Func <OioIdwsAuthorizationServiceOptions, HttpResponseMessage, Task> assert,
            Func <string> samlToken         = null,
            Func <ILoggerFactory> setLogger = null)
        {
            var requestSamlToken = samlToken != null?samlToken() : GetSamlTokenXml();

            var options = new OioIdwsAuthorizationServiceOptions
            {
                AccessTokenIssuerPath    = new PathString("/accesstoken/issue"),
                AccessTokenRetrievalPath = new PathString("/accesstoken"),
                IssuerAudiences          = () => Task.FromResult(issuerAudiences.ToArray()),
                CertificateValidator     = X509CertificateValidator.None //no reason for tests to require proper certificate validation
            };

            using (var server = TestServerWithClientCertificate.Create(() => clientCertificate, app =>
            {
                app.UseOioIdwsAuthorizationService(options);

                if (setLogger != null)
                {
                    app.SetLoggerFactory(setLogger());
                }
            }))
            {
                server.BaseAddress = new Uri("https://localhost/");

                var response = await server.HttpClient.PostAsync("/accesstoken/issue",
                                                                 new FormUrlEncodedContent(new[]
                {
                    new KeyValuePair <string, string>("saml-token", Utils.ToBase64(requestSamlToken)),
                }));

                await assert(options, response);
            }
        }
示例#3
0
        public async Task CallWspService_ShortTokenLifeTime_RenegotiatesAccessToken()
        {
            var serverEndpoint = "https://digst.oioidws.rest.wsp:10002";

            var tokensIssuedCount = 0;

            using (WebApp.Start(serverEndpoint, app =>
            {
                app.SetLoggerFactory(new ConsoleLoggerFactory());

                var tokenStore = new InMemorySecurityTokenStore();

                var tokenStoreWrapper = new Mock <ISecurityTokenStore>();
                tokenStoreWrapper
                .Setup(x => x.RetrieveTokenAsync(It.IsAny <string>()))
                .Returns((string accessToken) => tokenStore.RetrieveTokenAsync(accessToken));
                tokenStoreWrapper
                .Setup(x => x.StoreTokenAsync(It.IsAny <string>(), It.IsAny <OioIdwsToken>()))
                .Returns((string accessToken, OioIdwsToken token) =>
                {
                    tokensIssuedCount++;
                    return(tokenStore.StoreTokenAsync(accessToken, token));
                });

                var authorizationServerOptions = new OioIdwsAuthorizationServiceOptions
                {
                    AccessTokenIssuerPath = new PathString("/accesstoken/issue"),
                    IssuerAudiences = () => Task.FromResult(new[]
                    {
                        new IssuerAudiences("d9f10c97aa647727adb64a349bb037c5c23c9a7a", "test cert")
                        .Audience(new Uri("https://wsp.oioidws-net.dk")),
                    }),
                    SecurityTokenStore = tokenStoreWrapper.Object,
                    MaxClockSkew = TimeSpan.FromSeconds(10), //a little time skew is needed for trusting STS tokens
                };

                app
                .UseOioIdwsAuthentication(new OioIdwsAuthenticationOptions())
                .UseOioIdwsAuthorizationService(authorizationServerOptions)
                .Use(async(context, next) =>
                {
                    if (context.Request.User == null)
                    {
                        //we expect the service to REQUIRE authorization
                        context.Response.StatusCode = 401;
                        return;
                    }

                    var identity = (ClaimsIdentity)context.Request.User.Identity;
                    await context.Response.WriteAsync(identity.Claims
                                                      .Single(x => x.Type == "dk:gov:saml:attribute:CvrNumberIdentifier").Value);
                });
            }))
            {
                var settings = new OioIdwsClientSettings
                {
                    ClientCertificate         = CertificateUtil.GetCertificate("0E6DBCC6EFAAFF72E3F3D824E536381B26DEECF5"),
                    AudienceUri               = new Uri("https://wsp.oioidws-net.dk"),
                    AccessTokenIssuerEndpoint = new Uri(serverEndpoint + "/accesstoken/issue"),
                    SecurityTokenService      = new OioIdwsStsSettings
                    {
                        Certificate     = CertificateUtil.GetCertificate("d9f10c97aa647727adb64a349bb037c5c23c9a7a"),
                        EndpointAddress =
                            new Uri("https://SecureTokenService.test-nemlog-in.dk/SecurityTokenService.svc"),
                        TokenLifeTime = TimeSpan.FromMinutes(5)
                    },
                    DesiredAccessTokenExpiry = TimeSpan.FromSeconds(5), //set a very low token expiry time
                };

                var idwsClient = new OioIdwsClient(settings);

                {
                    var handler = (OioIdwsRequestHandler)idwsClient.CreateMessageHandler();

                    var httpClient = new HttpClient(handler)
                    {
                        BaseAddress = new Uri(serverEndpoint)
                    };

                    //first request, token should be valid
                    var response = await httpClient.GetAsync("/myservice");

                    Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
                    var str = await response.Content.ReadAsStringAsync();

                    Assert.AreEqual("34051178", str);
                }

                {
                    var handler = (OioIdwsRequestHandler)idwsClient.CreateMessageHandler();

                    var httpClient = new HttpClient(handler)
                    {
                        BaseAddress = new Uri(serverEndpoint)
                    };

                    Thread.Sleep(TimeSpan.FromSeconds(30));
                    //second request, time has passed, token should be renegotiated
                    var response = await httpClient.GetAsync("/myservice");

                    Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
                    var str = await response.Content.ReadAsStringAsync();

                    Assert.AreEqual("34051178", str);
                }

                Assert.AreEqual(2, tokensIssuedCount);
            }
        }
        public async Task IssueAccessToken_Success_ReturnsCorrectly()
        {
            var requestSamlToken = Utils.ToBase64("accesstoken1");
            var accessToken      = "dummy";

            var accessTokenGeneratorMock = new Mock <IKeyGenerator>();
            var tokenStoreMock           = new Mock <ISecurityTokenStore>();
            var tokenValidatorMock       = new Mock <ITokenValidator>();

            accessTokenGeneratorMock
            .Setup(x => x.GenerateUniqueKey())
            .Returns(accessToken);

            tokenValidatorMock
            .Setup(x => x.ValidateTokenAsync(Utils.FromBase64(requestSamlToken), It.IsAny <X509Certificate2>(), It.IsAny <OioIdwsAuthorizationServiceOptions>()))
            .ReturnsAsync(new TokenValidationResult
            {
                Success        = true,
                ClaimsIdentity = new ClaimsIdentity()
            });

            var tokenDataFormatMock = new Mock <ISecureDataFormat <AuthenticationProperties> >();

            tokenDataFormatMock
            .Setup(x => x.Protect(It.IsAny <AuthenticationProperties>()))
            .Returns(accessToken);

            var options = new OioIdwsAuthorizationServiceOptions
            {
                AccessTokenIssuerPath    = new PathString("/accesstoken/issue"),
                AccessTokenRetrievalPath = new PathString("/accesstoken"),
                KeyGenerator             = accessTokenGeneratorMock.Object,
                TokenValidator           = tokenValidatorMock.Object,
                IssuerAudiences          = () => Task.FromResult(new []
                {
                    new IssuerAudiences("thumbprint1", "name"),
                }),
                SecurityTokenStore = tokenStoreMock.Object,
                TokenDataFormat    = tokenDataFormatMock.Object,
            };

            using (var server = TestServer.Create(app =>
            {
                app.UseOioIdwsAuthorizationService(options);
            }))
            {
                server.BaseAddress = new Uri("https://localhost/");

                var response = await server.HttpClient.PostAsync("/accesstoken/issue",
                                                                 new FormUrlEncodedContent(new[]
                                                                                           { new KeyValuePair <string, string>("saml-token", requestSamlToken), }));

                Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
                Assert.IsNotNull(response.Content.Headers.ContentType);
                Assert.AreEqual("UTF-8", response.Content.Headers.ContentType.CharSet);
                Assert.AreEqual("application/json", response.Content.Headers.ContentType.MediaType);
                var accesssTokenFromResponse = JObject.Parse(await response.Content.ReadAsStringAsync());
                Assert.AreEqual(accessToken, accesssTokenFromResponse["access_token"]);
                Assert.AreEqual("Bearer", accesssTokenFromResponse["token_type"]);
                Assert.AreEqual((int)options.AccessTokenExpiration.TotalSeconds, accesssTokenFromResponse["expires_in"]);
            }

            accessTokenGeneratorMock.Verify(x => x.GenerateUniqueKey(), Times.Once);
        }
        public async Task IssueAccessToken_SetShouldExpireIn_HonorsExpiration()
        {
            var requestSamlToken = Utils.ToBase64("samltoken1");
            var expiration       = 200;

            var issuedAt = new DateTimeOffset(2016, 1, 1, 12, 00, 0, TimeSpan.Zero);

            AuthenticationProperties authenticationProperties = null; //set during token protection

            var clockMock = new Mock <ISystemClock>();

            clockMock.SetupGet(x => x.UtcNow).Returns(issuedAt);

            var tokenStoreMock = new Mock <ISecurityTokenStore>();

            var tokenValidatorMock = new Mock <ITokenValidator>();

            tokenValidatorMock
            .Setup(x => x.ValidateTokenAsync(Utils.FromBase64(requestSamlToken), It.IsAny <X509Certificate2>(), It.IsAny <OioIdwsAuthorizationServiceOptions>()))
            .ReturnsAsync(new TokenValidationResult
            {
                ClaimsIdentity  = new ClaimsIdentity(),
                AccessTokenType = AccessTokenType.Bearer,
                Success         = true,
            });

            var tokenDataFormatMock = new Mock <ISecureDataFormat <AuthenticationProperties> >();

            tokenDataFormatMock
            .Setup(x => x.Protect(It.IsAny <AuthenticationProperties>()))
            .Returns((AuthenticationProperties props) =>
            {
                authenticationProperties = props;
                return("tokenvalue");
            });

            var authorizationOptions = new OioIdwsAuthorizationServiceOptions
            {
                AccessTokenIssuerPath    = new PathString("/accesstoken/issue"),
                AccessTokenRetrievalPath = new PathString("/accesstoken"),
                IssuerAudiences          = () => Task.FromResult(new IssuerAudiences[0]),
                SecurityTokenStore       = tokenStoreMock.Object,
                TokenValidator           = tokenValidatorMock.Object,
                SystemClock     = clockMock.Object,
                TokenDataFormat = tokenDataFormatMock.Object,
            };

            using (var server = TestServer.Create(app =>
            {
                app.Use <OioIdwsAuthorizationServiceMiddleware>(app, authorizationOptions);
            }))
            {
                server.BaseAddress = new Uri("https://localhost/");

                var response = await server.HttpClient.PostAsync("/accesstoken/issue",
                                                                 new FormUrlEncodedContent(new Dictionary <string, string>
                {
                    { "saml-token", requestSamlToken },
                    { "should-expire-in", expiration.ToString() },
                }));

                Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
                var tokenInfo = JObject.Parse(await response.Content.ReadAsStringAsync());
                Assert.AreEqual(expiration, tokenInfo["expires_in"]);

                var expectedExpiration = issuedAt + TimeSpan.FromSeconds(expiration);
                Assert.AreEqual(expectedExpiration, authenticationProperties.ExpiresUtc);
                tokenStoreMock.Verify(v => v.StoreTokenAsync(authenticationProperties.Dictionary["value"], It.Is <OioIdwsToken>(x => x.ExpiresUtc == authenticationProperties.ExpiresUtc.Value)));
            }
        }
        public async Task RetrieveAccessToken_Success_TokenInformationIsInResponse()
        {
            var wspCertificate = CertificateUtil.GetCertificate("d9f10c97aa647727adb64a349bb037c5c23c9a7a");

            var accessToken     = "dummy";
            var oioIdwsTokenKey = "accesstoken1";
            var token           = new OioIdwsToken
            {
                Type       = AccessTokenType.Bearer,
                ExpiresUtc = DateTime.UtcNow.AddHours(1),
                Claims     = new[]
                {
                    new OioIdwsClaim
                    {
                        Type      = "type1",
                        Value     = "value1",
                        ValueType = "valuetype1",
                        Issuer    = "issuer1",
                    },
                    new OioIdwsClaim
                    {
                        Type      = "type2",
                        Value     = "value2",
                        ValueType = "valuetype2",
                        Issuer    = "issuer2",
                    },
                }
            };

            var tokenStoreMock = new Mock <ISecurityTokenStore>();

            tokenStoreMock
            .Setup(x => x.RetrieveTokenAsync(oioIdwsTokenKey))
            .ReturnsAsync(token);

            var tokenDataFormatMock = new Mock <ISecureDataFormat <AuthenticationProperties> >();

            tokenDataFormatMock
            .Setup(x => x.Unprotect(accessToken))
            .Returns(new AuthenticationProperties
            {
                ExpiresUtc = DateTimeOffset.UtcNow.AddHours(1),
                Dictionary = { { "value", oioIdwsTokenKey } }
            });

            var options = new OioIdwsAuthorizationServiceOptions
            {
                AccessTokenIssuerPath            = new PathString("/accesstoken/issue"),
                AccessTokenRetrievalPath         = new PathString("/accesstoken"),
                IssuerAudiences                  = () => Task.FromResult(new IssuerAudiences[0]),
                SecurityTokenStore               = tokenStoreMock.Object,
                TokenDataFormat                  = tokenDataFormatMock.Object,
                TrustedWspCertificateThumbprints = new[] { "d9f10c97aa647727adb64a349bb037c5c23c9a7a" },
                CertificateValidator             = X509CertificateValidator.None //no reason for tests to validate certs
            };

            using (var server = TestServerWithClientCertificate.Create(() => wspCertificate, app =>
            {
                app.UseOioIdwsAuthorizationService(options);
            }))
            {
                server.BaseAddress = new Uri("https://localhost/");

                var response = await server.HttpClient.GetAsync($"/accesstoken?{accessToken}");

                Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
                Assert.AreEqual("application/json", response.Content.Headers.ContentType.MediaType);
                var responseToken = JsonConvert.DeserializeObject <OioIdwsToken>(await response.Content.ReadAsStringAsync());

                Assert.AreEqual(token.Type, responseToken.Type);
                Assert.AreEqual(token.ExpiresUtc, responseToken.ExpiresUtc);
                Assert.AreEqual(token.Claims.Count, responseToken.Claims.Count);
                Assert.AreEqual(token.Claims.ElementAt(0).Type, responseToken.Claims.ElementAt(0).Type);
                Assert.AreEqual(token.Claims.ElementAt(0).Value, responseToken.Claims.ElementAt(0).Value);
            }
        }
        public async Task RetrieveAccessToken_ExpiredAccessToken_ReturnsUnauthorized()
        {
            var wspCertificate = CertificateUtil.GetCertificate("d9f10c97aa647727adb64a349bb037c5c23c9a7a");

            var accessToken     = "accessToken1";
            var oioIdwsTokenKey = "tokenValue1";

            var tokenInformation = new OioIdwsToken();

            var authProperties = new AuthenticationProperties
            {
                Dictionary = { { "value", oioIdwsTokenKey } }
            };

            var tokenDataFormatMock = new Mock <ISecureDataFormat <AuthenticationProperties> >();

            tokenDataFormatMock
            .Setup(x => x.Unprotect(accessToken))
            .Returns(() => authProperties);

            var currentTime = DateTimeOffset.UtcNow; //ensure static time during test

            var timeMock = new Mock <ISystemClock>();

            // ReSharper disable once AccessToModifiedClosure
            timeMock
            .SetupGet(x => x.UtcNow)
            .Returns(() => currentTime);

            var storeMock = new Mock <ISecurityTokenStore>();

            storeMock
            .Setup(x => x.RetrieveTokenAsync(oioIdwsTokenKey))
            .Returns(() => Task.FromResult(tokenInformation));

            var options = new OioIdwsAuthorizationServiceOptions
            {
                AccessTokenIssuerPath            = new PathString("/accesstoken/issue"),
                AccessTokenRetrievalPath         = new PathString("/accesstoken"),
                IssuerAudiences                  = () => Task.FromResult(new IssuerAudiences[0]),
                TrustedWspCertificateThumbprints = new[] { "d9f10c97aa647727adb64a349bb037c5c23c9a7a" },
                CertificateValidator             = X509CertificateValidator.None, //no reason for tests to validate certs
                TokenDataFormat                  = tokenDataFormatMock.Object,
                SystemClock        = timeMock.Object,
                MaxClockSkew       = TimeSpan.FromMinutes(5),
                SecurityTokenStore = storeMock.Object,
            };

            using (var server = TestServerWithClientCertificate.Create(() => wspCertificate, app =>
            {
                app.UseOioIdwsAuthorizationService(options);
            }))
            {
                server.BaseAddress = new Uri("https://localhost/");

                {
                    //test that token content is checked properly
                    authProperties.ExpiresUtc = currentTime - options.MaxClockSkew.Add(TimeSpan.FromSeconds(1));

                    var response = await server.HttpClient.GetAsync($"/accesstoken?{accessToken}");

                    Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode);
                    var json = JObject.Parse(await response.Content.ReadAsStringAsync());
                    Assert.AreEqual(1, json["expired"].Value <int>());
                }

                {
                    //test that stored token information is checked properly
                    authProperties.ExpiresUtc   = currentTime;
                    tokenInformation.ExpiresUtc = currentTime - options.MaxClockSkew.Add(TimeSpan.FromSeconds(1));

                    var response = await server.HttpClient.GetAsync($"/accesstoken?{accessToken}");

                    Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode);
                    var json = JObject.Parse(await response.Content.ReadAsStringAsync());
                    Assert.AreEqual(1, json["expired"].Value <int>());
                }
            }
        }
示例#8
0
        public async Task <TokenValidationResult> ValidateTokenAsync(string token, X509Certificate2 clientCertificate, OioIdwsAuthorizationServiceOptions options)
        {
            var samlHandler = new Saml2SecurityTokenHandler
            {
                Configuration = new SecurityTokenHandlerConfiguration
                {
                    ServiceTokenResolver = options.ServiceTokenResolver,
                }
            };

            Saml2SecurityToken securityToken;

            try
            {
                securityToken = ParseToken(token, samlHandler);
            }
            catch (Exception ex)
            {
                //If it's an CryptographicException the token might have been tampered with, e.g. signature validation failed
                //If it's an EncryptedTokenDecryptionFailedException the handler could not locate the proper certificate via the specified ServiceTokenResolver
                //Finally it could be a host of miscellanious errors during parsing which should not happen under normal circumstances. Either way, the exception is passed on for logging
                return(TokenValidationResult.Error("Token could not be parsed", ex));
            }

            var accessTokenType = DetermineAccessTokenType(securityToken);

            if (accessTokenType == AccessTokenType.HolderOfKey)
            {
                X509RawDataKeyIdentifierClause key;

                try
                {
                    key = GetSecurityKeyIdentifierClause(securityToken);
                }
                catch (Exception)
                {
                    return(TokenValidationResult.Error("X509Certificate used as SubjectConfirmationData could not read from the token"));
                }

                if (!key.Matches(clientCertificate))
                {
                    return(TokenValidationResult.Error("X509Certificate used as SubjectConfirmationData did not match the provided client certificate"));
                }
            }
            else if (accessTokenType == AccessTokenType.Bearer)
            {
                //no validation needed
            }
            else
            {
                return(TokenValidationResult.Error("SubjectConfirmation method was not valid"));
            }

            var issuerToken = securityToken.IssuerToken as X509SecurityToken;

            if (issuerToken == null)
            {
                return(TokenValidationResult.Error("IssuerToken is expected to be of type X509SecurityToken"));
            }

            var issuerAudience = (await options.IssuerAudiences()).SingleOrDefault(x => x.IssuerThumbprint == issuerToken.Certificate.Thumbprint?.ToLowerInvariant());

            if (issuerAudience == null)
            {
                return(TokenValidationResult.Error($"Issuer certificate '{issuerToken.Certificate.Thumbprint}' was unknown"));
            }

            samlHandler.Configuration.CertificateValidator      = options.CertificateValidator;
            samlHandler.Configuration.CertificateValidationMode = X509CertificateValidationMode.Custom;
            samlHandler.Configuration.MaxClockSkew        = options.MaxClockSkew;
            samlHandler.Configuration.IssuerNameRegistry  = new SpecificIssuerNameRegistry(issuerAudience.IssuerThumbprint, issuerAudience.IssuerFriendlyName);
            samlHandler.Configuration.AudienceRestriction = new AudienceRestriction();
            issuerAudience.ToList().ForEach(a => samlHandler.Configuration.AudienceRestriction.AllowedAudienceUris.Add(a));

            ClaimsIdentity identity;

            try
            {
                identity = samlHandler.ValidateToken(securityToken).First();
            }
            catch (AudienceUriValidationFailedException)
            {
                return(TokenValidationResult.Error("Audience was not known"));
            }
            catch (SecurityTokenExpiredException)
            {
                return(TokenValidationResult.Error("The Token is expired"));
            }
            catch (SecurityTokenValidationException ex)
            {
                //This covers whatever else validation errors might happen, such as token not yet valid and replay attacks
                return(TokenValidationResult.Error("The Token could not be validated", ex));
            }

            return(new TokenValidationResult
            {
                Success = true,
                AccessTokenType = accessTokenType.Value,
                ClaimsIdentity = identity,
            });
        }