public async Task CanLoadResource() { var location = new Uri("http://acme.d/acct/1"); var acct = new Account(); var expectedPayload = new JwsSigner(Helper.GetKeyV2()) .Sign("", null, location, "nonce"); var httpMock = new Mock <IAcmeHttpClient>(); var ctxMock = new Mock <IAcmeContext>(); ctxMock.SetupGet(m => m.HttpClient).Returns(httpMock.Object); ctxMock .Setup(c => c.Sign(It.IsAny <object>(), It.IsAny <Uri>())) .Callback((object payload, Uri loc) => { Assert.Null(payload); Assert.Equal(location, loc); }) .ReturnsAsync(expectedPayload); httpMock .Setup(m => m.Post <Account>(location, It.IsAny <JwsPayload>())) .Callback((Uri _, object o) => { var p = (JwsPayload)o; Assert.Equal(expectedPayload.Payload, p.Payload); Assert.Equal(expectedPayload.Protected, p.Protected); }) .ReturnsAsync(new AcmeHttpResponse <Account>(location, acct, default, default));
/// <summary> /// Posts the data to the specified URI. /// </summary> /// <typeparam name="T">The type of expected result</typeparam> /// <param name="client">The client.</param> /// <param name="jwsSigner">The jwsSigner used to sign the payload.</param> /// <param name="location">The URI.</param> /// <param name="entity">The payload.</param> /// <param name="ensureSuccessStatusCode">if set to <c>true</c>, throw exception if the request failed.</param> /// <param name="retryCount">Number of retries on badNonce errors (default = 1)</param> /// <returns> /// The response from ACME server. /// </returns> /// <exception cref="Exception"> /// If the HTTP request failed and <paramref name="ensureSuccessStatusCode"/> is <c>true</c>. /// </exception> internal static async Task <AcmeHttpResponse <T> > Post <T>(this IAcmeHttpClient client, JwsSigner jwsSigner, Uri location, object entity, bool ensureSuccessStatusCode, int retryCount = 1) { var payload = jwsSigner.Sign(entity, url: location, nonce: await client.ConsumeNonce()); var response = await client.Post <T>(location, payload); while (response.Error?.Status == System.Net.HttpStatusCode.BadRequest && response.Error.Type?.CompareTo("urn:ietf:params:acme:error:badNonce") == 0 && retryCount-- > 0) { payload = jwsSigner.Sign(entity, url: location, nonce: await client.ConsumeNonce()); response = await client.Post <T>(location, payload); } if (ensureSuccessStatusCode && response.Error != null) { throw new AcmeRequestException( string.Format(Strings.ErrorFetchResource, location), response.Error); } return(response); }
/// <summary> /// Changes the registration key. /// </summary> /// <param name="account">The account.</param> /// <param name="newKey">The new registration key.</param> /// <returns>The awaitable.</returns> public async Task ChangeKey(AcmeAccount account, KeyInfo newKey) { var keyPair = new AccountKey(newKey); var body = new { account = account.Location, newKey = keyPair.JsonWebKey, }; var jws = new JwsSigner(keyPair); var payload = jws.Sign(body); var payloadWithResourceType = new { payload.Header, payload.Payload, payload.Protected, payload.Signature, Resource = ResourceTypes.KeyChange }; var uri = await this.handler.GetResourceUri(ResourceTypes.KeyChange); var result = await this.handler.Post(uri, payloadWithResourceType, key); ThrowIfError(result); this.key = keyPair; }
/// <summary> /// Revokes the certificate. /// </summary> /// <param name="certificate">The certificate in DER format.</param> /// <param name="reason">The reason for revocation.</param> /// <param name="certificatePrivateKey">The certificate's private key.</param> /// <returns> /// The awaitable. /// </returns> public async Task RevokeCertificate(byte[] certificate, RevocationReason reason, IKey certificatePrivateKey) { var endpoint = await this.GetResourceUri(d => d.RevokeCert); var body = new CertificateRevocation { Certificate = JwsConvert.ToBase64String(certificate), Reason = reason }; JwsPayload payload; if (certificatePrivateKey != null) { var jws = new JwsSigner(certificatePrivateKey); payload = jws.Sign(body, url: endpoint, nonce: await HttpClient.ConsumeNonce()); } else { payload = await Sign(body, endpoint); } await HttpClient.Post <string>(endpoint, payload, true); }
/// <summary> /// Post to the new account endpoint. /// </summary> /// <param name="context">The ACME context.</param> /// <param name="body">The payload.</param> /// <param name="ensureSuccessStatusCode">if set to <c>true</c>, throw exception if the request failed.</param> /// <param name="eabKeyId">Optional key identifier, if using external account binding.</param> /// <param name="eabKey">Optional EAB key, if using external account binding.</param> /// <param name="eabKeyAlg">Optional EAB key algorithm, if using external account binding, defaults to HS256 if not specified</param> /// <returns>The ACME response.</returns> internal static async Task <AcmeHttpResponse <Account> > NewAccount( IAcmeContext context, Account body, bool ensureSuccessStatusCode, string eabKeyId = null, string eabKey = null, string eabKeyAlg = null) { var endpoint = await context.GetResourceUri(d => d.NewAccount); var jws = new JwsSigner(context.AccountKey); if (eabKeyId != null && eabKey != null) { var header = new { alg = eabKeyAlg?.ToUpper() ?? "HS256", kid = eabKeyId, url = endpoint }; var headerJson = Newtonsoft.Json.JsonConvert.SerializeObject(header, Newtonsoft.Json.Formatting.None, JsonUtil.CreateSettings()); var protectedHeaderBase64 = JwsConvert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(headerJson)); var accountKeyBase64 = JwsConvert.ToBase64String( System.Text.Encoding.UTF8.GetBytes( Newtonsoft.Json.JsonConvert.SerializeObject(context.AccountKey.JsonWebKey, Newtonsoft.Json.Formatting.None) ) ); var signingBytes = System.Text.Encoding.ASCII.GetBytes($"{protectedHeaderBase64}.{accountKeyBase64}"); // eab signature is the hash of the header and account key, using the eab key byte[] signatureHash; switch (header.alg) { case "HS512": using (var hs512 = new HMACSHA512(JwsConvert.FromBase64String(eabKey))) signatureHash = hs512.ComputeHash(signingBytes); break; case "HS384": using (var hs384 = new HMACSHA384(JwsConvert.FromBase64String(eabKey))) signatureHash = hs384.ComputeHash(signingBytes); break; default: using (var hs256 = new HMACSHA256(JwsConvert.FromBase64String(eabKey))) signatureHash = hs256.ComputeHash(signingBytes); break; } var signatureBase64 = JwsConvert.ToBase64String(signatureHash); body.ExternalAccountBinding = new { Protected = protectedHeaderBase64, Payload = accountKeyBase64, Signature = signatureBase64 }; } return(await context.HttpClient.Post <Account>(jws, endpoint, body, ensureSuccessStatusCode)); }
/// <summary> /// Signs the data with account key. /// </summary> /// <param name="entity">The data to sign.</param> /// <param name="uri">The URI for the request.</param> /// <returns>The JWS payload.</returns> public async Task <JwsPayload> Sign(object entity, Uri uri) { var nonce = await HttpClient.ConsumeNonce(); var location = await Account().Location(); var jws = new JwsSigner(AccountKey); return(jws.Sign(entity, location, uri, nonce)); }
/// <summary> /// Post to the new account endpoint. /// </summary> /// <param name="context">The ACME context.</param> /// <param name="body">The payload.</param> /// <param name="ensureSuccessStatusCode">if set to <c>true</c>, throw exception if the request failed.</param> /// <returns>The ACME response.</returns> internal static async Task <AcmeHttpResponse <Account> > NewAccount( IAcmeContext context, Account body, bool ensureSuccessStatusCode) { var endpoint = await context.GetResourceUri(d => d.NewAccount); var jws = new JwsSigner(context.AccountKey); var payload = jws.Sign(body, url: endpoint, nonce: await context.HttpClient.ConsumeNonce()); return(await context.HttpClient.Post <Account>(endpoint, payload, ensureSuccessStatusCode)); }
public async Task CanLoadChallenges() { var authz = new Authorization { Challenges = new[] { new Challenge { Url = new Uri("http://acme.d/c/1"), Token = "token", Type = "dns-01" }, new Challenge { Url = new Uri("http://acme.d/c/1"), Token = "token", Type = "http-01" } } }; var expectedPayload = new JwsSigner(Helper.GetKeyV2()) .Sign("", null, location, "nonce"); contextMock.Reset(); httpClientMock.Reset(); contextMock .Setup(c => c.GetDirectory()) .ReturnsAsync(Helper.MockDirectoryV2); contextMock .SetupGet(c => c.AccountKey) .Returns(Helper.GetKeyV2()); contextMock .SetupGet(c => c.BadNonceRetryCount) .Returns(1); contextMock .Setup(c => c.Sign(It.IsAny <object>(), location)) .Callback((object payload, Uri loc) => { Assert.Null(payload); Assert.Equal(location, loc); }) .ReturnsAsync(expectedPayload); contextMock.SetupGet(c => c.HttpClient).Returns(httpClientMock.Object); httpClientMock .Setup(m => m.Post <Authorization>(location, It.IsAny <JwsPayload>())) .Callback((Uri _, object o) => { var p = (JwsPayload)o; Assert.Equal(expectedPayload.Payload, p.Payload); Assert.Equal(expectedPayload.Protected, p.Protected); }) .ReturnsAsync(new AcmeHttpResponse <Authorization>(location, authz, default, default));
public async Task RevokeCertificateAsync(Certificate certificate, RevocationReason reason = RevocationReason.Unspecified) { var certificateRevocation = new CertificateRevocation { Certificate = JwsConvert.ToBase64String(certificate.GetOriginalCertificate()), Reason = reason }; var directory = await GetDirectoryAsync(); var nonce = await GetNonceAsync(); var signedData = new JwsSigner(certificate.Key).Sign(certificateRevocation, url: directory.RevokeCert, nonce: nonce); var result = await PostAsync <Empty>(directory.RevokeCert, signedData); }
public async Task CanLoadResource() { var expectedAccount = new Account(); var expectedPayload = new JwsSigner(Helper.GetKeyV2()) .Sign(new Account(), null, location, "nonce"); contextMock.Reset(); httpClientMock.Reset(); contextMock .Setup(c => c.GetDirectory()) .ReturnsAsync(Helper.MockDirectoryV2); contextMock .SetupGet(c => c.AccountKey) .Returns(Helper.GetKeyV2()); contextMock.SetupGet(c => c.HttpClient).Returns(httpClientMock.Object); contextMock .Setup(c => c.Sign(It.IsAny <object>(), location)) .Callback((object payload, Uri loc) => { Assert.Equal( JsonConvert.SerializeObject(new Account()), JsonConvert.SerializeObject(payload)); Assert.Equal(location, loc); }) .ReturnsAsync(expectedPayload); httpClientMock .Setup(c => c.ConsumeNonce()) .ReturnsAsync("nonce"); httpClientMock .Setup(c => c.Post <Account>(location, It.IsAny <JwsPayload>())) .Callback((Uri _, object o) => { var p = (JwsPayload)o; Assert.Equal(expectedPayload.Payload, p.Payload); Assert.Equal(expectedPayload.Protected, p.Protected); }) .ReturnsAsync(new AcmeHttpResponse <Account>(location, expectedAccount, null, null)); var instance = new AccountContext(contextMock.Object, location); var account = await instance.Resource(); Assert.Equal(expectedAccount, account); }
public async Task CanLoadAuthorizations() { var order = new Order { Authorizations = new[] { new Uri("http://acme.d/acct/1/authz/1"), new Uri("http://acme.d/acct/1/authz/2"), } }; var expectedPayload = new JwsSigner(Helper.GetKeyV2()) .Sign("", null, location, "nonce"); contextMock.Reset(); httpClientMock.Reset(); contextMock .Setup(c => c.GetDirectory()) .ReturnsAsync(Helper.MockDirectoryV2); contextMock .SetupGet(c => c.AccountKey) .Returns(Helper.GetKeyV2()); contextMock .SetupGet(c => c.BadNonceRetryCount) .Returns(1); contextMock.SetupGet(c => c.HttpClient).Returns(httpClientMock.Object); contextMock .Setup(c => c.Sign(It.IsAny <object>(), It.IsAny <Uri>())) .Callback((object payload, Uri loc) => { Assert.Null(payload); Assert.Equal(location, loc); }) .ReturnsAsync(expectedPayload); httpClientMock .Setup(m => m.Post <Order>(location, It.IsAny <JwsPayload>())) .Callback((Uri _, object o) => { var p = (JwsPayload)o; Assert.Equal(expectedPayload.Payload, p.Payload); Assert.Equal(expectedPayload.Protected, p.Protected); }) .ReturnsAsync(new AcmeHttpResponse <Order>(location, order, default, default));
/// <summary> /// Revokes the certificate. /// </summary> /// <param name="certificate">The certificate in DER format.</param> /// <param name="reason">The reason for revocation.</param> /// <param name="certificatePrivateKey">The certificate's private key.</param> /// <returns> /// The awaitable. /// </returns> public async Task RevokeCertificate(byte[] certificate, RevocationReason reason, IKey certificatePrivateKey) { var endpoint = await this.GetResourceUri(d => d.RevokeCert); var body = new CertificateRevocation { Certificate = JwsConvert.ToBase64String(certificate), Reason = reason }; if (certificatePrivateKey != null) { var jws = new JwsSigner(certificatePrivateKey); await HttpClient.Post <string>(jws, endpoint, body, true); } else { await HttpClient.Post <string>(this, endpoint, body, true); } }
/// <summary> /// Changes the account key. /// </summary> /// <param name="key">The new account key.</param> /// <returns>The account resource.</returns> public async Task <Account> ChangeKey(IKey key) { var endpoint = await this.GetResourceUri(d => d.KeyChange); var location = await Account().Location(); var newKey = key ?? KeyFactory.NewKey(defaultKeyType); var keyChange = new { account = location, oldKey = AccountKey.JsonWebKey, }; var jws = new JwsSigner(newKey); var body = jws.Sign(keyChange, url: endpoint); var resp = await HttpClient.Post <Account>(this, endpoint, body, true); AccountKey = newKey; return(resp.Resource); }
public void TestGenerateSignature() { var decodedPrivateKey = UrlBase64.Decode(TestPrivateKey); var privateKey = ECKeyHelper.GetPrivateKey(decodedPrivateKey); var header = new Dictionary <string, object>(); header.Add("typ", "JWT"); header.Add("alg", "ES256"); var jwtPayload = new Dictionary <string, object>(); jwtPayload.Add("aud", "aud"); jwtPayload.Add("exp", 1); jwtPayload.Add("sub", "subject"); var signer = new JwsSigner(privateKey); var token = signer.GenerateSignature(header, jwtPayload); var tokenParts = token.Split('.'); Assert.AreEqual(3, tokenParts.Length); var encodedHeader = tokenParts[0]; var encodedPayload = tokenParts[1]; var signature = tokenParts[2]; var decodedHeader = Encoding.UTF8.GetString(UrlBase64.Decode(encodedHeader)); var decodedPayload = Encoding.UTF8.GetString(UrlBase64.Decode(encodedPayload)); Assert.AreEqual(@"{""typ"":""JWT"",""alg"":""ES256""}", decodedHeader); Assert.AreEqual(@"{""aud"":""aud"",""exp"":1,""sub"":""subject""}", decodedPayload); var decodedSignature = UrlBase64.Decode(signature); var decodedSignatureLength = decodedSignature.Length; var isSignatureLengthValid = decodedSignatureLength == 66 || decodedSignatureLength == 64; Assert.AreEqual(true, isSignatureLengthValid); }
/// <summary> /// This method takes the required VAPID parameters and returns the required /// header to be added to a Web Push Protocol Request. /// </summary> /// <param name="audience">This must be the origin of the push service.</param> /// <param name="subject">This should be a URL or a 'mailto:' email address</param> /// <param name="publicKey">The VAPID public key as a base64 encoded string</param> /// <param name="privateKey">The VAPID private key as a base64 encoded string</param> /// <param name="expiration">The expiration of the VAPID JWT.</param> /// <returns>A dictionary of header key/value pairs.</returns> public static Dictionary <string, string> GetVapidHeaders(string audience, string subject, string publicKey, string privateKey, long expiration = -1) { ValidateAudience(audience); ValidateSubject(subject); ValidatePublicKey(publicKey); ValidatePrivateKey(privateKey); var decodedPrivateKey = UrlBase64.Decode(privateKey); if (expiration == -1) { expiration = UnixTimeNow() + 43200; } else { ValidateExpiration(expiration); } var header = new Dictionary <string, object> { { "typ", "JWT" }, { "alg", "ES256" } }; var jwtPayload = new Dictionary <string, object> { { "aud", audience }, { "exp", expiration }, { "sub", subject } }; var signingKey = ECKeyHelper.GetPrivateKey(decodedPrivateKey); var signer = new JwsSigner(signingKey); var token = signer.GenerateSignature(header, jwtPayload); var results = new Dictionary <string, string> { { "Authorization", "WebPush " + token }, { "Crypto-Key", "p256ecdsa=" + publicKey } }; return(results); }
public async Task CanLoadOrderList() { var loc = new Uri("http://acme.d/acct/1/orders"); var account = new Account { Orders = loc }; var expectedPayload = new JwsSigner(Helper.GetKeyV2()) .Sign(new Account(), null, location, "nonce"); contextMock.Reset(); httpClientMock.Reset(); contextMock .Setup(c => c.GetDirectory(false)) .ReturnsAsync(Helper.MockDirectoryV2); contextMock .SetupGet(c => c.AccountKey) .Returns(Helper.GetKeyV2()); contextMock.SetupGet(c => c.HttpClient).Returns(httpClientMock.Object); contextMock .Setup(c => c.Sign(It.IsAny <object>(), location)) .ReturnsAsync(expectedPayload); httpClientMock .Setup(c => c.ConsumeNonce()) .ReturnsAsync("nonce"); httpClientMock .Setup(c => c.Post <Account>(location, It.IsAny <JwsPayload>())) .ReturnsAsync(new AcmeHttpResponse <Account>(location, account, null, null)); var ctx = new AccountContext(contextMock.Object, location); var orders = await ctx.Orders(); Assert.IsType <OrderListContext>(orders); Assert.Equal(loc, orders.Location); }
/// <summary> /// Encodes the specified entity for ACME requests. /// </summary> /// <param name="entity">The entity.</param> /// <param name="keyPair">The key pair.</param> /// <param name="nonce">The nonce.</param> /// <returns>The encoded JSON.</returns> private static object Encode(object entity, IAccountKey keyPair, string nonce) { var encoder = new JwsSigner(keyPair.SignatureKey); return(encoder.Sign(entity, nonce)); }
public Account(RsaKeyPair key) : this() { Key = key; Signer = new JwsSigner(Key); }