public async Task ValidES256Signature_Unsupported() { var ex = await Assert.ThrowsAsync <InvalidOperationException>(() => JsonWebSignature.VerifySignedTokenAsync( FakeCertificateCache.Es256ForIap, BuildOptions(new MockClock(FakeCertificateCache.ValidEs256ForIap)))); Assert.Equal("ES256 signed token verification is not supported in this platform.", ex.Message); }
public async Task ValidAudience() { var options = BuildOptions(trustedAudiences: new string[] { "233772281425-ab2mcbiqmv8kh0mdnqsrkrod97qk37h0.apps.googleusercontent.com" }); var payload = await JsonWebSignature.VerifySignedTokenAsync(FakeCertificateCache.JwtGoogleSigned, options); Assert.NotNull(payload); }
public void POST_Nonce_Empty() { AcmeResponse response = null; using var provider = Application.CreateProvider(); IAcmeController controller = (IAcmeController)provider.GetService(typeof(IAcmeController)); var accountKey = RSA.Create(2048); var token = new JsonWebSignature(); token.SetProtected(new JsonWebSignatureProtected { Url = $"{Application.BaseAddress}new-acct", Algorithm = AlgorithmsEnum.RS256, Key = new JsonWebKey(accountKey), }); token.Sign(accountKey); response = controller.CreateAccount(new AcmeRequest { Path = $"{Application.BaseAddress}new-acct", Method = "POST", Token = token, }); Assert.Equal(400, response.StatusCode); var content = response.GetContent <Protocol.Error>(); Assert.Equal(Protocol.ErrorType.Malformed, content.Type); Assert.NotNull(content.Detail); }
/// <summary> /// Rotates the current Public key that is associated with this Account by the target ACME CA with a new Public key. /// </summary> /// <see cref="https://tools.ietf.org/html/draft-ietf-acme-acme-18#section-7.3.5"/> public async Task <AcmeResponse <Protocol.Account> > AccountChangeKeyAsync(AsymmetricAlgorithm keyNew) { Logger.Info("Changing an ACME account key. Params:{@params}", keyNew); var jws = new JsonWebSignature(); var jwk = new JsonWebKey(Key); var jwkNew = new JsonWebKey(keyNew); jws.SetPayload(new Protocol.Messages.ChangeKey { Account = Location, Key = jwk, }); jws.SetProtected(new JsonWebSignatureProtected { Algorithm = AlgorithmsEnum.RS256, Url = Directory.KeyChange, Key = jwkNew, }); jws.Sign(keyNew); var response = await Request(GetType(typeof(Protocol.Account)), Directory.KeyChange, new RequestParams { Method = HttpMethod.Post, Payload = jws }); Key = keyNew; return(response); }
public async Task OidcTokenProvider_ComputeCredential() { // This test only executes on GCE so we are certain to have a ComputeCredential. GoogleCredential credential = GoogleCredential.FromComputeCredential(); OidcToken token = await credential.GetOidcTokenAsync( OidcTokenOptions.FromTargetAudience("https://this.is.a.test")); // Check an access token (really id_token) is available. Assert.NotNull(await token.GetAccessTokenAsync()); // If IdToken is set and AccessToken is not, AccessToken is set to // IdToken, so we can always check here that AccessToken is not null. Assert.NotNull(token.TokenResponse.AccessToken); // The enpoint does not send an expiry, bu we set it to the id_token // expiry. Assert.NotNull(token.TokenResponse.ExpiresInSeconds); var verificationOptions = new SignedTokenVerificationOptions(); verificationOptions.TrustedAudiences.Add("https://this.is.a.test"); var payload = await JsonWebSignature.VerifySignedTokenAsync(await token.GetAccessTokenAsync(), verificationOptions); Assert.NotNull(payload); Assert.Contains("https://this.is.a.test", payload.AudienceAsList); }
public async Task Validate_Signature_Time() { var clockInvalid1 = new MockClock(FakeCertificateCache.BeforeValidJwtGoogleSigned); var clockValid1 = new MockClock(FakeCertificateCache.ValidJwtGoogleSigned); var clockInvalid2 = new MockClock(FakeCertificateCache.AfterValidJwtGoogleSigned); // Test with no tolerance Assert.NotNull(await JsonWebSignature.VerifySignedTokenAsync(FakeCertificateCache.JwtGoogleSigned, BuildOptions(clockValid1))); var ex1 = await Assert.ThrowsAsync <InvalidJwtException>(() => JsonWebSignature.VerifySignedTokenAsync(FakeCertificateCache.JwtGoogleSigned, BuildOptions(clockInvalid1))); Assert.Equal("JWT is not yet valid.", ex1.Message); var ex2 = await Assert.ThrowsAsync <InvalidJwtException>(() => JsonWebSignature.VerifySignedTokenAsync(FakeCertificateCache.JwtGoogleSigned, BuildOptions(clockInvalid2))); Assert.Equal("JWT has expired.", ex2.Message); // Test with tolerance await Assert.ThrowsAsync <InvalidJwtException>(() => JsonWebSignature.VerifySignedTokenAsync(FakeCertificateCache.JwtGoogleSigned, BuildOptions(clockInvalid1, clockTolerance: TimeSpan.FromSeconds(109)))); Assert.NotNull(await JsonWebSignature.VerifySignedTokenAsync(FakeCertificateCache.JwtGoogleSigned, BuildOptions(clockInvalid1, clockTolerance: TimeSpan.FromSeconds(111)))); Assert.NotNull(await JsonWebSignature.VerifySignedTokenAsync(FakeCertificateCache.JwtGoogleSigned, BuildOptions(clockInvalid2, clockTolerance: TimeSpan.FromSeconds(11)))); await Assert.ThrowsAsync <InvalidJwtException>(() => JsonWebSignature.VerifySignedTokenAsync(FakeCertificateCache.JwtGoogleSigned, BuildOptions(clockInvalid2, clockTolerance: TimeSpan.FromSeconds(9)))); }
/// <summary> /// Account create with external account binding. /// </summary> /// <see cref="https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.3.5"/> public async Task <AcmeResponse <Protocol.Account> > AccountCreateAsync(Protocol.Messages.NewAccount account, string kid, string keyMac) { Logger.Info("Creating a new ACME account with external account binding. Params:{@params}", new { Account = account, KeyId = kid, KeyMac = keyMac, }); var jws = new JsonWebSignature(); jws.SetProtected(new JsonWebSignatureProtected { Algorithm = AlgorithmsEnum.HS256, KeyID = kid, Url = Directory.NewAccount, }); jws.SetPayload(new JsonWebKey(Key)); var key = HMAC.Create("HMACSHA256"); key.Key = Base64Url.Decode(keyMac); jws.Sign(key); account.ExternalAccountBinding = jws; return(await AccountCreateAsync(account)); }
public async Task InvalidAudience() { var options = BuildOptions(trustedAudiences: new string[] { "audience1", "audience2" }); var ex = await Assert.ThrowsAsync <InvalidJwtException>(() => JsonWebSignature.VerifySignedTokenAsync(FakeCertificateCache.JwtGoogleSigned, options)); Assert.Equal("JWT contains untrusted 'aud' claim.", ex.Message); }
public async Task ValidRS256Signature_CustomPayload() { var payload = await JsonWebSignature.VerifySignedTokenAsync <CustomPayload>(FakeCertificateCache.JwtGoogleSigned, BuildOptions()); Assert.NotNull(payload); Assert.NotNull(payload.AuthorizedParty); Assert.NotEmpty(payload.AuthorizedParty); }
public async Task InvalidIssuer() { var options = BuildOptions(trustedIssuers: new string[] { "issuer1", "issuer2" }); var ex = await Assert.ThrowsAsync <InvalidJwtException>(() => JsonWebSignature.VerifySignedTokenAsync(FakeCertificateCache.JwtGoogleSigned, options)); Assert.Equal("JWT issuer incorrect. Must be one of: 'issuer1', 'issuer2'", ex.Message); }
public async Task WrongAlgorithm() { string jwt = $"{UrlSafeBase64Encode("{\"alg\":\"HS256\"}")}.{UrlSafeBase64Encode("{}")}."; var ex = await Assert.ThrowsAsync <InvalidJwtException>(() => JsonWebSignature.VerifySignedTokenAsync(jwt, BuildOptions())); Assert.Equal("Signing algorithm must be either RS256 or ES256.", ex.Message); }
public ActionResult PostOrders([FromBody] JsonWebSignature token) { var response = Controller.PostOrders(GetAcmeRequest(token)); ProcessOrders(response); return(CreateActionResult(response)); }
private AcmeRequest GetAcmeRequest(JsonWebSignature token) { return(new AcmeRequest(token) { Method = Request.Method, Query = GetQuery(), Path = UriHelper.GetDisplayUrl(Request), }); }
protected AcmeRequest GetAcmeRequest(JsonWebSignature token) { return(new AcmeRequest(token) { Method = Request.Method.Method, Query = GetQuery(), Path = Request.RequestUri.ToString(), }); }
public async Task InvalidES256Signature_Unsupported() { // Lets just change the last character of the signed token to break the signature. // But Headers and Payload are still valid so we guarantee that the only // failure comes from the signature being invalid. // The last character was an A, changing for a 4. If at some point the token used in tests changes // and the new token ends in anything but a 4, this test will still be valid. If the new token ends in a 4 // this test will fail and then we'll have to replace the 4 by an A or any other character. var invalidToken = FakeCertificateCache.Es256ForIap.Substring(0, FakeCertificateCache.Es256ForIap.Length - 1) + "4"; var ex = await Assert.ThrowsAsync <InvalidOperationException>(() => JsonWebSignature.VerifySignedTokenAsync(invalidToken, BuildOptions(new MockClock(FakeCertificateCache.ValidEs256ForIap)))); Assert.Equal("ES256 signed token verification is not supported in this platform.", ex.Message); }
public async Task InvalidRS256Signature() { // Lets just change the last character of the Google signed token to break the signature. // But Headers and Payload are still valid for a Google token so we guarantee that the only // failure comes from the signature being invalid. // The last character was a Q, changing for a 4. If at some point the token used in tests changes // and the new token ends in anything but a 4, this test will still be valid. If the new token ends in a 4 // this test will fail and then we'll have to replace the 4 by a Q or any other character. var invalidToken = FakeCertificateCache.JwtGoogleSigned.Substring(0, FakeCertificateCache.JwtGoogleSigned.Length - 1) + "4"; var ex = await Assert.ThrowsAsync <InvalidJwtException>(() => JsonWebSignature.VerifySignedTokenAsync(invalidToken, BuildOptions())); Assert.Equal("JWT invalid, unable to verify signature.", ex.Message); }
public void Account_New_TermsOfService_Enabled_TermsOfServiceAgreed_False() { AcmeResponse response = null; using var provider = Application.CreateProvider(o => { o.TermsOfService = "https://some.com/acme/terms.pdf"; }); IAcmeController controller = (IAcmeController)provider.GetService(typeof(IAcmeController)); // Get nonce response = controller.GetNonce(new AcmeRequest { Path = $"{Application.BaseAddress}new-nonce", Method = "HEAD", }); // Create token var accountKey = RSA.Create(2048); var token = new JsonWebSignature(); token.SetProtected(new JsonWebSignatureProtected { Url = $"{Application.BaseAddress}new-acct", Algorithm = AlgorithmsEnum.RS256, Key = new JsonWebKey(accountKey), Nonce = response.Headers.ReplayNonce, }); token.SetPayload(new NewAccount { Contacts = new string[] { "mailto:[email protected]" }, TermsOfServiceAgreed = false, }); token.Sign(accountKey); // Create account response = controller.CreateAccount(new AcmeRequest { Path = $"{Application.BaseAddress}new-acct", Method = "POST", Token = token, }); Assert.Equal(400, response.StatusCode); var content = response.GetContent <Protocol.Error>(); Assert.Equal(Protocol.ErrorType.Malformed, content.Type); Assert.NotNull(content.Detail); }
/// <summary> /// Verifies a signed jwt token and returns its payload. /// </summary> /// <param name="signedJwt">The token to verify.</param> /// <param name="expectedAudience">The audience that the token should be meant for. /// Validation will fail if that's not the case.</param> /// <param name="cancellationToken">The cancellation token to propagate cancellation requests.</param> /// <returns>A task that when completed will have as its result the payload of the verified token.</returns> /// <exception cref="InvalidJwtException">If verification failed. The message of the exception will contain /// information as to why the token failed.</exception> public async Task <JsonWebSignature.Payload> VerifyTokenAsync( string signedJwt, string expectedAudience, CancellationToken cancellationToken = default) { SignedTokenVerificationOptions options = new SignedTokenVerificationOptions { // Use clock tolerance to account for possible clock differences // between the issuer and the verifier. IssuedAtClockTolerance = TimeSpan.FromMinutes(1), ExpiryClockTolerance = TimeSpan.FromMinutes(1), TrustedAudiences = { expectedAudience } }; return(await JsonWebSignature.VerifySignedTokenAsync(signedJwt, options, cancellationToken : cancellationToken)); }
public void Account_New_TermsOfService_Disabled() { AcmeResponse response = null; using var provider = Application.CreateProvider(); IAcmeController controller = (IAcmeController)provider.GetService(typeof(IAcmeController)); // Get nonce response = controller.GetNonce(new AcmeRequest { Path = $"{Application.BaseAddress}new-nonce", Method = "HEAD", }); // Create token var accountKey = RSA.Create(2048); var token = new JsonWebSignature(); token.SetProtected(new JsonWebSignatureProtected { Url = $"{Application.BaseAddress}new-acct", Algorithm = AlgorithmsEnum.RS256, Key = new JsonWebKey(accountKey), Nonce = response.Headers.ReplayNonce, }); token.SetPayload(new NewAccount { Contacts = new string[] { "mailto:[email protected]" }, }); token.Sign(accountKey); // Create account response = controller.CreateAccount(new AcmeRequest { Path = $"{Application.BaseAddress}new-acct", Method = "POST", Token = token, }); Assert.Equal(201, response.StatusCode); var content = response.GetContent <Protocol.Account>(); Assert.Single(response.Headers.Link); Assert.Equal("https://test.server.com/acme/acct/1", response.Headers.Location); Assert.NotNull(content); Assert.NotNull(content.Key); Assert.Null(content.TermsOfServiceAgreed); }
public ActionResult GetCertificate([FromBody] JsonWebSignature token, string id) { var response = Controller.GetCertificate(GetAcmeRequest(token), id); if (response.Content is MediaTypeContent content) { foreach (var header in response.Headers.AllKeys) { Response.Headers.Add(header, response.Headers.Get(header)); } return(new FileStreamResult(content.Content, content.Type)); } else { return(CreateActionResult(response)); } }
public void POST_Url_WrongTargetUrl() { /// The value of the "url" header /// parameter MUST be a string representing the target URL AcmeResponse response = null; using var provider = Application.CreateProvider(); IAcmeController controller = (IAcmeController)provider.GetService(typeof(IAcmeController)); // Get nonce response = controller.GetNonce(new AcmeRequest { Path = $"{Application.BaseAddress}new-nonce", Method = "HEAD", }); var accountKey = RSA.Create(2048); var token = new JsonWebSignature(); token.SetProtected(new JsonWebSignatureProtected { Url = "https://wrong.com/acme/new-account", Nonce = response.Headers.ReplayNonce, Algorithm = AlgorithmsEnum.RS256, Key = new JsonWebKey(accountKey), }); token.Sign(accountKey); response = controller.CreateAccount(new AcmeRequest { Path = $"{Application.BaseAddress}new-acct", Method = "POST", Token = token, }); Assert.Equal(400, response.StatusCode); var content = response.GetContent <Protocol.Error>(); Assert.Equal(Protocol.ErrorType.Malformed, content.Type); Assert.NotNull(content.Detail); }
/// <inheritdoc/> public IExternalAccount Validate(JsonWebKey accountKey, JsonWebSignature token) { #region Check arguments if (accountKey is null) { throw new ArgumentNullException(nameof(accountKey)); } if (token is null) { throw new ArgumentNullException(nameof(token)); } #endregion var @prtoected = token.GetProtected(); var eabPayload = token.GetPayload <JsonWebKey>(); if (!eabPayload.Equals(accountKey)) { throw new MalformedException("Signed content in externalAccountBinding doesn't match to requirement"); // TODO check rfc error } var externalAccount = GetById(@prtoected.KeyID); if (externalAccount.Status != Protocol.ExternalAccountStatus.Pending) { throw new MalformedException("External account has wrong status"); // TODO check rfc error } var key = Base64Url.Decode(externalAccount.Key); externalAccount.Status = token.Verify(key) ? Protocol.ExternalAccountStatus.Valid : Protocol.ExternalAccountStatus.Invalid; ExternalAccountRepository.Update(externalAccount); Logger.Info("External account {id} status updated to {status}", externalAccount.Id, externalAccount.Status); return(externalAccount); }
public async Task <string> Run(string targetAudience, string credentialsFilePath, string uri) { ServiceAccountCredential saCredential; using (var fs = new FileStream(credentialsFilePath, FileMode.Open, FileAccess.Read)) { saCredential = ServiceAccountCredential.FromServiceAccountData(fs); } OidcToken oidcToken = await saCredential.GetOidcTokenAsync(OidcTokenOptions.FromTargetAudience(targetAudience).WithTokenFormat(OidcTokenFormat.Standard)).ConfigureAwait(false); string token = await oidcToken.GetAccessTokenAsync().ConfigureAwait(false); // the following snippet verifies an id token. // this step is done on the receiving end of the oidc endpoint // adding this step in here as just as a demo on how to do this //var options = SignedTokenVerificationOptions.Default; SignedTokenVerificationOptions options = new SignedTokenVerificationOptions { IssuedAtClockTolerance = TimeSpan.FromMinutes(1), ExpiryClockTolerance = TimeSpan.FromMinutes(1), TrustedAudiences = { targetAudience }, CertificatesUrl = "https://www.googleapis.com/oauth2/v3/certs" // default value }; var payload = await JsonWebSignature.VerifySignedTokenAsync(token, options); Console.WriteLine("Verified with audience " + payload.Audience); // end verification // use the token using (var httpClient = new HttpClient()) { httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); string response = await httpClient.GetStringAsync(uri).ConfigureAwait(false); Console.WriteLine(response); return(response); } }
public void POST_Nonce_WrongEncoding() { AcmeResponse response = null; using var provider = Application.CreateProvider(); IAcmeController controller = (IAcmeController)provider.GetService(typeof(IAcmeController)); var accountKey = RSA.Create(2048); var token = new JsonWebSignature(); token.SetProtected(new JsonWebSignatureProtected { Url = $"{Application.BaseAddress}new-acct", Algorithm = AlgorithmsEnum.RS256, Key = new JsonWebKey(accountKey), Nonce = "Wrong encoded nonce" }); token.Sign(accountKey); response = controller.CreateAccount(new AcmeRequest { Path = $"{Application.BaseAddress}new-acct", Method = "POST", Token = token, }); // If the value of a "nonce" header parameter is not valid // according to this encoding, then the verifier MUST reject the JWS as // malformed. Assert.Equal(400, response.StatusCode); var content = response.GetContent <Protocol.Error>(); Assert.Equal(Protocol.ErrorType.Malformed, content.Type); Assert.NotNull(content.Detail); }
/// <summary> /// Signs JWT token using the private key and returns the serialized assertion. /// </summary> /// <param name="payload">the JWT payload to sign.</param> private string CreateAssertionFromPayload(JsonWebSignature.Payload payload) { string serializedHeader = CreateSerializedHeader(); string serializedPayload = NewtonsoftJsonSerializer.Instance.Serialize(payload); StringBuilder assertion = new StringBuilder(); assertion.Append(UrlSafeBase64Encode(serializedHeader)) .Append(".") .Append(UrlSafeBase64Encode(serializedPayload)); // Sign the header and the payload. var hashAlg = new SHA256CryptoServiceProvider(); byte[] assertionHash = hashAlg.ComputeHash(Encoding.ASCII.GetBytes(assertion.ToString())); var signature = UrlSafeBase64Encode(key.SignHash(assertionHash, "2.16.840.1.101.3.4.2.1" /* SHA256 OIG */)); assertion.Append(".").Append(signature); return assertion.ToString(); }
/// <summary> /// Signs JWT token using the private key and returns the serialized assertion. /// </summary> /// <param name="payload">the JWT payload to sign.</param> private string CreateAssertionFromPayload(JsonWebSignature.Payload payload) { string serializedHeader = CreateSerializedHeader(); string serializedPayload = NewtonsoftJsonSerializer.Instance.Serialize(payload); StringBuilder assertion = new StringBuilder(); assertion.Append(UrlSafeBase64Encode(serializedHeader)) .Append(".") .Append(UrlSafeBase64Encode(serializedPayload)); // Sign the header and the payload. var hashAlg = SHA256.Create(); byte[] assertionHash = hashAlg.ComputeHash(Encoding.ASCII.GetBytes(assertion.ToString())); #if NETSTANDARD var sigBytes = key.SignHash(assertionHash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); #else var sigBytes = key.SignHash(assertionHash, Sha256Oig); #endif var signature = UrlSafeBase64Encode(sigBytes); assertion.Append(".").Append(signature); return assertion.ToString(); }
public async Task ValidES256Signature() => Assert.NotNull(await JsonWebSignature.VerifySignedTokenAsync( FakeCertificateCache.Es256ForIap, BuildOptions(new MockClock(FakeCertificateCache.ValidEs256ForIap))));
public async Task ValidRS256Signature() => Assert.NotNull(await JsonWebSignature.VerifySignedTokenAsync( FakeCertificateCache.JwtGoogleSigned, BuildOptions()));
public ActionResult RevokeCertificate([FromBody] JsonWebSignature token) { var response = Controller.RevokeCertificate(GetAcmeRequest(token)); return(CreateActionResult(response)); }
public async Task TwoPartToken() => await Assert.ThrowsAsync <InvalidJwtException>(() => JsonWebSignature.VerifySignedTokenAsync("header.payload", BuildOptions()));
public async Task EmptyToken() => await Assert.ThrowsAsync <ArgumentException>(() => JsonWebSignature.VerifySignedTokenAsync("", BuildOptions()));
public async Task NullToken() => await Assert.ThrowsAsync <ArgumentNullException>(() => JsonWebSignature.VerifySignedTokenAsync(null, BuildOptions()));