protected async Task CanGenerateCertificateWithEC(KeyAlgorithm algo) { var dirUri = await GetAcmeUriV2(); var hosts = new[] { $"www-ec-{DomainSuffix}.{algo}.certes-ci.dymetis.com".ToLower() }; var ctx = new AcmeContext(dirUri, GetKeyV2(algo), http: GetAcmeHttpClient(dirUri)); var orderCtx = await AuthorizeHttp(ctx, hosts); var certKey = KeyFactory.NewKey(algo); var finalizedOrder = await orderCtx.Finalize(new CsrInfo { CountryName = "CA", State = "Ontario", Locality = "Toronto", Organization = "Certes", OrganizationUnit = "Dev", CommonName = hosts[0], }, certKey); var cert = await orderCtx.Download(null); var x509 = new X509Certificate2(cert.Certificate.ToDer()); Assert.Contains(hosts[0], x509.Subject); // deactivate authz so the subsequence can trigger challenge validation await ClearAuthorizations(orderCtx); }
public async Task ThrowWhenFinalizeFailed() { var pem = File.ReadAllText("./Data/cert-es256.pem"); var orderCtxMock = new Mock <IOrderContext>(); orderCtxMock.Setup(m => m.Download()).ReturnsAsync(new CertificateChain(pem)); orderCtxMock.Setup(m => m.Resource()).ReturnsAsync(new Order { Identifiers = new[] { new Identifier { Value = "www.certes.com", Type = IdentifierType.Dns }, }, Status = OrderStatus.Pending, }); orderCtxMock.Setup(m => m.Finalize(It.IsAny <byte[]>())) .ReturnsAsync(new Order { Identifiers = new[] { new Identifier { Value = "www.certes.com", Type = IdentifierType.Dns }, }, Status = OrderStatus.Invalid, }); var key = KeyFactory.NewKey(KeyAlgorithm.RS256); await Assert.ThrowsAsync <AcmeException>(() => orderCtxMock.Object.Generate(new CsrInfo { CountryName = "C", CommonName = "www.certes.com", }, key)); }
public async Task CanGenerateWildcard() { var dirUri = await GetAcmeUriV2(); var hosts = new[] { $"*.wildcard-{DomainSuffix}.es256.certes-ci.dymetis.com" }; var ctx = new AcmeContext(dirUri, GetKeyV2(), http: GetAcmeHttpClient(dirUri)); var orderCtx = await AuthzDns(ctx, hosts); var certKey = KeyFactory.NewKey(KeyAlgorithm.RS256); var finalizedOrder = await orderCtx.Finalize(new CsrInfo { CountryName = "CA", State = "Ontario", Locality = "Toronto", Organization = "Certes", OrganizationUnit = "Dev", CommonName = hosts[0], }, certKey); var pem = await orderCtx.Download(null); var builder = new PfxBuilder(pem.Certificate.ToDer(), certKey); foreach (var issuer in pem.Issuers) { builder.AddIssuer(issuer.ToDer()); } builder.AddTestCerts(); var pfx = builder.Build("ci", "abcd1234"); Assert.NotNull(pfx); }
/// <summary> /// Initializes a new instance of the <see cref="AcmeContext" /> class. /// </summary> /// <param name="directoryUri">The directory URI.</param> /// <param name="accountKey">The account key.</param> /// <param name="http">The HTTP client.</param> /// <param name="badNonceRetryCount">The number of retries on a bad nonce.</param> /// <exception cref="ArgumentNullException"> /// If <paramref name="directoryUri"/> is <c>null</c>. /// </exception> public AcmeContext(Uri directoryUri, IKey accountKey = null, IAcmeHttpClient http = null, int badNonceRetryCount = 1) { DirectoryUri = directoryUri ?? throw new ArgumentNullException(nameof(directoryUri)); AccountKey = accountKey ?? KeyFactory.NewKey(defaultKeyType); HttpClient = http ?? new AcmeHttpClient(directoryUri); BadNonceRetryCount = badNonceRetryCount; }
public void CanGenerateDnsRecordValue() { var key = KeyFactory.NewKey(KeyAlgorithm.ES256); using (var sha256 = SHA256.Create()) { Assert.Equal( JwsConvert.ToBase64String(sha256.ComputeHash(Encoding.UTF8.GetBytes(key.KeyAuthorization("token")))), key.DnsTxt("token")); } }
public async Task CanGenerateCertificateWhenOrderPending() { var pem = File.ReadAllText("./Data/cert-es256.pem"); var orderCtxMock = new Mock <IOrderContext>(); orderCtxMock.Setup(m => m.Download()).ReturnsAsync(new CertificateChain(pem)); orderCtxMock.Setup(m => m.Resource()).ReturnsAsync(new Order { Identifiers = new[] { new Identifier { Value = "www.certes.com", Type = IdentifierType.Dns }, }, Status = OrderStatus.Pending, }); orderCtxMock.Setup(m => m.Finalize(It.IsAny <byte[]>())) .ReturnsAsync(new Order { Identifiers = new[] { new Identifier { Value = "www.certes.com", Type = IdentifierType.Dns }, }, Status = OrderStatus.Valid, }); var key = KeyFactory.NewKey(KeyAlgorithm.RS256); var certInfo = await orderCtxMock.Object.Generate(new CsrInfo { CountryName = "C", CommonName = "www.certes.com", }, key); Assert.Equal( pem.Where(c => !char.IsWhiteSpace(c)), certInfo.Certificate.ToPem().Where(c => !char.IsWhiteSpace(c))); var certInfoNoCn = await orderCtxMock.Object.Generate(new CsrInfo { CountryName = "C", }, key); Assert.Equal( pem.Where(c => !char.IsWhiteSpace(c)), certInfoNoCn.Certificate.ToPem().Where(c => !char.IsWhiteSpace(c))); }
public async Task Account() { var acmeDir = await IntegrationHelper.GetAcmeUriV2(); var accountKey = Helper.GetKeyV2(KeyAlgorithm.RS256); var httpClient = IntegrationHelper.GetAcmeHttpClient(acmeDir); var acme = new AcmeContext(acmeDir, accountKey, httpClient); var account = await acme.Account(); var order = await acme.NewOrder(new[] { "www.certes-ci.dymetis.com" }); var authz = (await order.Authorizations()).First(); var httpChallenge = await authz.Http(); var token = httpChallenge.Token; var keyAuthz = httpChallenge.KeyAuthz; var orderUri = order.Location; await httpChallenge.Validate(); var res = await authz.Resource(); while (res.Status != AuthorizationStatus.Valid && res.Status != AuthorizationStatus.Invalid) { res = await authz.Resource(); } acme = new AcmeContext(acmeDir, accountKey, httpClient); order = acme.Order(orderUri); var privateKey = KeyFactory.NewKey(KeyAlgorithm.ES256); var cert = await order.Generate(new CsrInfo { CountryName = "CA", State = "Ontario", Locality = "Toronto", Organization = "Certes", OrganizationUnit = "Dev", CommonName = "www.certes-ci.dymetis.com", }, privateKey, null); var pfxBuilder = cert.ToPfx(privateKey); pfxBuilder.AddTestCerts(); var pfx = pfxBuilder.Build("my-cert", "abcd1234"); }
public async Task CanChangeAccountKey() { var dirUri = await GetAcmeUriV2(); var ctx = new AcmeContext(dirUri, http: GetAcmeHttpClient(dirUri)); var account = await ctx.NewAccount( new[] { $"mailto:certes-{DateTime.UtcNow.Ticks}@example.com" }, true); var location = await ctx.Account().Location(); var newKey = KeyFactory.NewKey(KeyAlgorithm.ES256); await ctx.ChangeKey(newKey); var ctxWithNewKey = new AcmeContext(dirUri, newKey, http: GetAcmeHttpClient(dirUri)); var locationWithNewKey = await ctxWithNewKey.Account().Location(); Assert.Equal(location, locationWithNewKey); }
/// <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 async Task ThrowWhenOrderNotReady() { var orderCtxMock = new Mock <IOrderContext>(); orderCtxMock.Setup(m => m.Resource()).ReturnsAsync(new Order { Identifiers = new[] { new Identifier { Value = "www.certes.com", Type = IdentifierType.Dns }, }, Status = OrderStatus.Valid, }); var key = KeyFactory.NewKey(KeyAlgorithm.RS256); await Assert.ThrowsAsync <AcmeException>(() => orderCtxMock.Object.Generate(new CsrInfo { CountryName = "C", CommonName = "www.certes.com", }, key)); }
public async Task CanGenerateWildcard() { var dirUri = await GetAcmeUriV2(); var hosts = new[] { $"wildcard-{DomainSuffix}.es256.certes-ci.dymetis.com" }; var ctx = new AcmeContext(dirUri, GetKeyV2(), http: GetAcmeHttpClient(dirUri)); var orderCtx = await AuthzDns(ctx, hosts); var certKey = KeyFactory.NewKey(KeyAlgorithm.RS256); var finalizedOrder = await orderCtx.Finalize(new CsrInfo { CountryName = "CA", State = "Ontario", Locality = "Toronto", Organization = "Certes", OrganizationUnit = "Dev", CommonName = hosts[0], }, certKey); var pem = await orderCtx.Download(); }
public async Task CanGenerateCertificateHttp() { var dirUri = await GetAcmeUriV2(); var hosts = new[] { $"www-http-{DomainSuffix}.es256.certes-ci.dymetis.com", $"mail-http-{DomainSuffix}.es256.certes-ci.dymetis.com" }; var ctx = new AcmeContext(dirUri, GetKeyV2(), http: GetAcmeHttpClient(dirUri)); var orderCtx = await AuthorizeHttp(ctx, hosts); var certKey = KeyFactory.NewKey(KeyAlgorithm.RS256); var finalizedOrder = await orderCtx.Finalize(new CsrInfo { CountryName = "CA", State = "Ontario", Locality = "Toronto", Organization = "Certes", OrganizationUnit = "Dev", CommonName = hosts[0], }, certKey); var certChain = await orderCtx.Download(null); var pfxBuilder = certChain.ToPfx(certKey); pfxBuilder.AddIssuers(IntegrationHelper.TestCertificates); var pfx = pfxBuilder.Build("my-pfx", "abcd1234"); // revoke certificate var certParser = new X509CertificateParser(); var certificate = certParser.ReadCertificate(certChain.Certificate.ToDer()); var der = certificate.GetEncoded(); await ctx.RevokeCertificate(der, RevocationReason.Unspecified, null); // deactivate authz so the subsequence can trigger challenge validation await ClearAuthorizations(orderCtx); }
public async Task CanGenerateCertificateTlsAlpn() { var dirUri = await GetAcmeUriV2(); var hosts = new[] { $"{DomainSuffix}.tls-alpn.certes-ci.dymetis.com" }; var ctx = new AcmeContext(dirUri, GetKeyV2(), http: GetAcmeHttpClient(dirUri)); var orderCtx = await ctx.NewOrder(hosts); var order = await orderCtx.Resource(); Assert.NotNull(order); Assert.Equal(hosts.Length, order.Authorizations?.Count); Assert.True( OrderStatus.Ready == order.Status || OrderStatus.Pending == order.Status || OrderStatus.Processing == order.Status, $"Invalid order status: {order.Status}"); var authrizations = await orderCtx.Authorizations(); foreach (var authzCtx in authrizations) { var authz = await authzCtx.Resource(); var tlsAlpnChallenge = await authzCtx.TlsAlpn(); var alpnCertKey = KeyFactory.NewKey(KeyAlgorithm.ES256); var alpnCert = ctx.AccountKey.TlsAlpnCertificate(tlsAlpnChallenge.Token, authz.Identifier.Value, alpnCertKey); await SetupValidationResponder(authz, alpnCert, alpnCertKey); await tlsAlpnChallenge.Validate(); } while (true) { await Task.Delay(100); var statuses = new List <AuthorizationStatus>(); foreach (var authz in authrizations) { var a = await authz.Resource(); statuses.Add(a.Status ?? AuthorizationStatus.Pending); } if (statuses.All(s => s == AuthorizationStatus.Valid || s == AuthorizationStatus.Invalid)) { break; } } var certKey = KeyFactory.NewKey(KeyAlgorithm.RS256); var finalizedOrder = await orderCtx.Finalize(new CsrInfo { CountryName = "CA", State = "Ontario", Locality = "Toronto", Organization = "Certes", OrganizationUnit = "Dev", CommonName = hosts[0], }, certKey); var certChain = await orderCtx.Download(null); var pfxBuilder = certChain.ToPfx(certKey); pfxBuilder.AddTestCerts(); var pfx = pfxBuilder.Build("my-pfx", "abcd1234"); // revoke certificate var certParser = new X509CertificateParser(); var certificate = certParser.ReadCertificate(certChain.Certificate.ToDer()); var der = certificate.GetEncoded(); await ctx.RevokeCertificate(der, RevocationReason.Unspecified, null); // deactivate authz so the subsequence can trigger challenge validation foreach (var authz in authrizations) { var authzRes = await authz.Deactivate(); Assert.Equal(AuthorizationStatus.Deactivated, authzRes.Status); } }
protected async Task CanGenerateCertificateWithEC(KeyAlgorithm algo) { var dirUri = await GetAcmeUriV2(); var hosts = new[] { $"www-ec-{DomainSuffix}.{algo}.certes-ci.dymetis.com".ToLower() }; var ctx = new AcmeContext(dirUri, GetKeyV2(algo), http: GetAcmeHttpClient(dirUri)); var orderCtx = await ctx.NewOrder(hosts); var order = await orderCtx.Resource(); Assert.NotNull(order); Assert.Equal(hosts.Length, order.Authorizations?.Count); Assert.True(OrderStatus.Pending == order.Status || OrderStatus.Processing == order.Status); var authrizations = await orderCtx.Authorizations(); foreach (var authz in authrizations) { var httpChallenge = await authz.Http(); await httpChallenge.Validate(); } while (true) { await Task.Delay(100); var statuses = new List <AuthorizationStatus>(); foreach (var authz in authrizations) { var a = await authz.Resource(); statuses.Add(a.Status ?? AuthorizationStatus.Pending); } if (statuses.All(s => s == AuthorizationStatus.Valid || s == AuthorizationStatus.Invalid)) { break; } } var certKey = KeyFactory.NewKey(algo); var finalizedOrder = await orderCtx.Finalize(new CsrInfo { CountryName = "CA", State = "Ontario", Locality = "Toronto", Organization = "Certes", OrganizationUnit = "Dev", CommonName = hosts[0], }, certKey); var cert = await orderCtx.Download(); var x509 = new X509Certificate2(cert.Certificate.ToDer()); Assert.Contains(hosts[0], x509.Subject); // deactivate authz so the subsequence can trigger challenge validation foreach (var authz in authrizations) { var authzRes = await authz.Deactivate(); Assert.Equal(AuthorizationStatus.Deactivated, authzRes.Status); } }
public async Task CanGenerateWithAlternateLink() { var defaultpem = File.ReadAllText("./Data/defaultLeaf.pem"); var alternatepem = File.ReadAllText("./Data/alternateLeaf.pem"); var accountLoc = new System.Uri("http://acme.d/account/101"); var orderLoc = new System.Uri("http://acme.d/order/101"); var finalizeLoc = new System.Uri("http://acme.d/order/101/finalize"); var certDefaultLoc = new System.Uri("http://acme.d/order/101/cert/1234"); var certAlternateLoc = new System.Uri("http://acme.d/order/101/cert/1234/1"); var alternates = new [] { new { key = "alternate", value = certDefaultLoc }, new { key = "alternate", value = certAlternateLoc }, }.ToLookup(x => x.key, x => x.value); var httpClientMock = new Mock <IAcmeHttpClient>(); httpClientMock.Setup(m => m.Post <string>(certDefaultLoc, It.IsAny <object>())) .ReturnsAsync(new AcmeHttpResponse <string>( accountLoc, defaultpem, alternates, null)); httpClientMock.Setup(m => m.Post <string>(certAlternateLoc, It.IsAny <object>())) .ReturnsAsync(new AcmeHttpResponse <string>( accountLoc, alternatepem, alternates, null)); httpClientMock.Setup(m => m.Post <Order>(finalizeLoc, It.IsAny <object>())) .ReturnsAsync(new AcmeHttpResponse <Order>( accountLoc, new Order { Identifiers = new[] { new Identifier { Value = "www.certes.com", Type = IdentifierType.Dns }, }, Status = OrderStatus.Valid, }, null, null)); var acmeContextMock = new Mock <IAcmeContext>(); acmeContextMock.SetupGet(x => x.HttpClient) .Returns(httpClientMock.Object); var orderCtxMock = new Mock <OrderContext>(acmeContextMock.Object, orderLoc); orderCtxMock.Setup(m => m.Resource()).ReturnsAsync(new Order { Identifiers = new[] { new Identifier { Value = "www.certes.com", Type = IdentifierType.Dns }, }, Certificate = certDefaultLoc, Finalize = finalizeLoc, Status = OrderStatus.Pending, }); var key = KeyFactory.NewKey(KeyAlgorithm.RS256); var certInfoDefaultRoot = await orderCtxMock.Object.Generate(new CsrInfo { CountryName = "C", CommonName = "www.certes.com", }, key, null); Assert.Equal( defaultpem.Where(c => !char.IsWhiteSpace(c)), certInfoDefaultRoot.Certificate.ToPem().Where(c => !char.IsWhiteSpace(c))); var certInfoAlternateRoot = await orderCtxMock.Object.Generate(new CsrInfo { CountryName = "C", CommonName = "www.certes.com", }, key, "AlternateRoot"); Assert.Equal( alternatepem.Where(c => !char.IsWhiteSpace(c)), certInfoAlternateRoot.Certificate.ToPem().Where(c => !char.IsWhiteSpace(c))); var certInfoUnknownRoot = await orderCtxMock.Object.Generate(new CsrInfo { CountryName = "C", CommonName = "www.certes.com", }, key, "UnknownRoot"); Assert.Equal( defaultpem.Where(c => !char.IsWhiteSpace(c)), certInfoUnknownRoot.Certificate.ToPem().Where(c => !char.IsWhiteSpace(c))); }