示例#1
0
        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));
示例#2
0
        /// <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);
        }
示例#3
0
        /// <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;
        }
示例#4
0
        /// <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);
        }
示例#5
0
        /// <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));
        }
示例#6
0
        /// <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));
        }
示例#7
0
        /// <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));
示例#9
0
        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);
        }
示例#10
0
        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);
        }
示例#11
0
        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));
示例#12
0
        /// <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);
            }
        }
示例#13
0
        /// <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);
        }
示例#15
0
        /// <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);
        }
示例#16
0
        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);
        }
示例#17
0
        /// <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));
        }
示例#18
0
 public Account(RsaKeyPair key)
     : this()
 {
     Key    = key;
     Signer = new JwsSigner(Key);
 }