/// <summary> /// Request a certificate. /// </summary> /// <param name="order">A previously completed order.</param> /// <param name="key">The private key to sign the certificate request with.</param> /// <param name="cancellationToken">Cancellation token for the async request.</param> /// <returns>An updated <see cref="Order"/> object.</returns> /// <remarks>The subjectname for the request is the first identifier in <paramref name="order"/>. Subsequent identifiers are added as alternative names.</remarks> public async Task <Order> RequestCertificateAsync(Order order, RSACryptoServiceProvider key, CancellationToken cancellationToken = default) { var csr = new CertificateRequest("CN=" + order.Identifiers[0].Value, key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); var san = new SubjectAlternativeNameBuilder(); foreach (var identifier in order.Identifiers.Skip(1)) { san.AddDnsName(identifier.Value); } csr.CertificateExtensions.Add(san.Build()); var message = new FinalizeRequest { CSR = Jws.Base64UrlEncoded(csr.CreateSigningRequest()) }; var(result, responseText) = await client.PostAsync <Order>(order.Finalize, message, cancellationToken); if (result is Order acmeOrder) { return(acmeOrder); } throw new InvalidServerResponse("Invalid response from server during RequestCertificate.", responseText, order.Finalize.ToString()); }
public AcmeClient(HttpClient client, RSA key) { Info($"using server {client.BaseAddress}"); this.client = client ?? throw new ArgumentNullException(nameof(client)); jws = new Jws(key); }
/// <summary> /// Initializes a new instance of the <see cref="ACMERestClient"/> class. /// </summary> /// <param name="jws">Jws key to use.</param> /// <param name="logger">Optional logger.</param> public ACMERestClient(Jws jws) { var client = typeof(ACMEClient); var runtimeVersion = client.Assembly.GetCustomAttribute <AssemblyFileVersionAttribute>(); this.jws = jws; UserAgent = $"{client.FullName}/{runtimeVersion.Version.ToString()} ({RuntimeInformation.OSDescription} {RuntimeInformation.ProcessArchitecture})"; }
public LetsEncryptClient(string url) { _url = url ?? throw new ArgumentNullException(nameof(url)); var home = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData, Environment.SpecialFolderOption.Create); var hash = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(url)); var file = Jws.Base64UrlEncoded(hash) + ".lets-encrypt.cache.json"; _path = Path.Combine(home, file); }
private async Task <JwsEncodedMessage> GetSignedMessageAsync(string url) { Nonce nonce = await GetNonceAsync(); var header = GetMessageHeader(url, nonce); return(Jws.Sign( integrityProtected: Base64Url.Encode(header), payload: string.Empty, privateKey: _privateKey )); }
private async Task <JwsEncodedMessage> GetSignedMessageAsync(string url, JsonObject payload) { Nonce nonce = await GetNonceAsync(); var header = GetMessageHeader(url, nonce); return(Jws.Sign( integrityProtectedHeader: header, payload: payload, privateKey: _privateKey )); }
/// <summary> /// Initializes a new instance of the <see cref="ACMEClient"/> class. /// </summary> /// <param name="endpoint">The selected endpoint of the ACME server.</param> /// <param name="rsaKey">Encryption key for account.</param> /// <param name="accountId">Account id.</param> /// <param name="restClientFactory">An instance of a <see cref="IRESTClient"/> for API requests.</param> public ACMEClient(string endpoint, RSA rsaKey, string accountId, IRestClientFactory restClientFactory) { if (endpoint == null) { throw new ArgumentNullException(nameof(endpoint)); } this.endpoint = new Uri(endpoint); this.rsaKey = rsaKey ?? throw new ArgumentNullException(nameof(rsaKey)); jws = new Jws(rsaKey, accountId); client = restClientFactory.CreateRestClient(jws); }
public async Task <string> GetDnsChallenge(string hostname, CancellationToken token = default(CancellationToken)) { _hosts.Add(hostname); var challenge = await _client.NewDnsAuthorizationAsync(hostname, token); var dnsChallenge = challenge.Challenges.First(x => x.Type == "dns-01"); var keyToken = _client.GetKeyAuthorization(dnsChallenge.Token); var computedDns = Jws.Base64UrlEncoded(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(keyToken))); _challenges.Add(dnsChallenge); return(computedDns); }
public async Task ValidateSignature(Jws jws) { if (jws.Algorithm == JwsAlgorithm.none) { return; } var signedBytes = GetBytes(jws.RawSignedPart); var signature = Base64Url.DeserializeBytes(jws.RawSignature, "Token signature"); var validator = ValidatorFactory.Create(jws.Header, jws.Algorithm); var keys = (await Metadata.JsonWebKeys()).Keys; validator.Validate(signedBytes, signature, keys); }
// TODO: double check https://tools.ietf.org/html/rfc7515#section-5.2 point 5 public void ValidateJoseHeader(Jws jws) { // The JWS Signature value is not valid if the "alg" value does not represent // a supported algorithm (https://tools.ietf.org/html/rfc7515#section-4.1.1) Logger.ThrowIf(jws.Header.Alg.IsEmpty(), "Missing JWS Algorithm"); Logger.ThrowIf(jws.Algorithm == JwsAlgorithm.Unknown, "Unknown JWS Algorithm: {0}", jws.Header.Alg); // TODO: During this step, verify that the resulting JOSE Header does not contain duplicate Header Parameter names. // https://tools.ietf.org/html/rfc7515#section-5.2 - Point 4 // With the current json implementation identifying these duplications is not possible Logger.ThrowIf(jws.Header.Crit != null, "JWS \"crit\" extensions are not supported yet"); // https://tools.ietf.org/html/rfc7516#section-9 Logger.ThrowIf(jws.Header.Enc != null, "The JWS header contains \"enc\" key which is used by JWE tokens"); }
public async Task <CertificateResponse> NewCertificateRequestAsync(byte[] csr, CancellationToken token = default(CancellationToken)) { await EnsureDirectoryAsync().ConfigureAwait(false); var request = new AcmeCertificateRequest { Csr = Jws.Base64UrlEncoded(csr) }; var response = await PostAsync <CertificateResponse>( _directory.NewCertificate, request, token ).ConfigureAwait(false); return(response); }
public async Task Init(string email, CancellationToken token = default(CancellationToken)) { _accountKey = new RSACryptoServiceProvider(4096); _client = GetCachedClient(_url); (_directory, _) = await SendAsync <Directory>(HttpMethod.Get, new Uri("directory", UriKind.Relative), null, token); // Use this when testing against pebble //(_directory, _) = await SendAsync<Directory>(HttpMethod.Get, new Uri("dir", UriKind.Relative), null, token); if (File.Exists(_path)) { bool success; try { lock (Locker) { _cache = JsonConvert.DeserializeObject <RegistrationCache>(File.ReadAllText(_path)); } _accountKey.ImportCspBlob(_cache.AccountKey); // From: https://community.letsencrypt.org/t/acme-v2-strict-jws-kid-header-processing/63321 // "KeyID headers contain the full account URL as returned by the Location header in a newAccount response" var kid = _cache.Location.ToString(); _jws = new Jws(_accountKey, kid); success = true; } catch { success = false; // if we failed for any reason, we'll just // generate a new registration } if (success) { return; } } _jws = new Jws(_accountKey, null); var(account, response) = await SendAsync <Account>(HttpMethod.Post, _directory.NewAccount, new Account { // we validate this in the UI before we get here, so that is fine TermsOfServiceAgreed = true, Contacts = new[] { "mailto:" + email }, }, token); _jws.SetKeyId(account); if (account.Status != "valid") { throw new InvalidOperationException("Account status is not valid, was: " + account.Status + Environment.NewLine + response); } lock (Locker) { _cache = new RegistrationCache { Location = account.Location, AccountKey = _accountKey.ExportCspBlob(true), Id = account.Id, Key = account.Key }; File.WriteAllText(_path, JsonConvert.SerializeObject(_cache, Formatting.Indented)); } }
public AcmeClient(HttpClient client, RSA key) { _client = client ?? throw new ArgumentNullException(nameof(client)); _jws = new Jws(key); }
public void Ensure_key_authorization_is_correct() { var jws = new Jws(GetPrivateKey()); Assert.Equal("token.pdmN_UI10XD6wy44jm-JkHmJOFxevse_2jio8cH1lRw", jws.GetKeyAuthorization("token")); }
/// <summary> /// Creates an instance of <see cref="ACMERestClient"/>. /// </summary> /// <param name="jws">Jws key used to sign requests.</param> /// <returns>An instance of <see cref="ACMERestClient"/></returns> public IRestClient CreateRestClient(Jws jws) { return(new ACMERestClient(jws)); }