private Byte[] signed_request(String url, PmlDictionary payload) { RegisterKey(); String payload64 = urlbase64(Encoding.UTF8.GetBytes(PmlJsonWriter.EncodeMessage(payload))); using (WebClient wc = new WebClient()) { String nonce = Interlocked.Exchange(ref replay_nonce, null); if (nonce == null) { wc.DownloadString(acme_url + "/directory"); nonce = wc.ResponseHeaders["Replay-Nonce"]; } RSAParameters account_key_params = account_key.ExportParameters(false); String pubExponent64 = urlbase64(account_key_params.Exponent); String pubMod64 = urlbase64(account_key_params.Modulus); PmlDictionary header = new PmlDictionary() { { "alg", "RS256" }, { "jwk", new PmlDictionary() { { "e", pubExponent64 }, { "kty", "RSA" }, { "n", pubMod64 } } } }; PmlDictionary prot = new PmlDictionary() { { "alg", "RS256" }, { "jwk", new PmlDictionary() { { "e", pubExponent64 }, { "kty", "RSA" }, { "n", pubMod64 } } }, { "nonce", nonce } }; String protected64 = urlbase64(Encoding.ASCII.GetBytes(PmlJsonWriter.EncodeMessage(prot))); String signed64; using (SHA256 sha = SHA256.Create()) signed64 = urlbase64(account_key.SignData(Encoding.ASCII.GetBytes(protected64 + "." + payload64), sha)); PmlDictionary data = new PmlDictionary() { { "header", header }, { "protected", protected64 }, { "payload", payload64 }, { "signature", signed64 } }; Byte[] ret = wc.UploadData(url, Encoding.UTF8.GetBytes(PmlJsonWriter.EncodeMessage(data))); if (!String.IsNullOrEmpty(wc.ResponseHeaders["Replay-Nonce"])) { replay_nonce = wc.ResponseHeaders["Replay-Nonce"]; } return(ret); } }
public void AuthorizeDomains(String[] domains, CreateHTTPChallengeCallback challenge_callback) { RegisterKey(); RSAParameters account_key_params = account_key.ExportParameters(false); String thumbprint; using (SHA256 sha = SHA256.Create()) thumbprint = urlbase64(sha.ComputeHash(Encoding.UTF8.GetBytes(PmlJsonWriter.EncodeMessage(new PmlDictionary() { { "e", urlbase64(account_key_params.Exponent) }, { "kty", "RSA" }, { "n", urlbase64(account_key_params.Modulus) } })))); foreach (String altname in domains) { Byte[] response_string = signed_request(acme_url + "/acme/new-authz", new PmlDictionary() { { "resource", "new-authz" }, { "identifier", new PmlDictionary() { { "type", "dns" }, { "value", altname } } } }); PmlDictionary response = (PmlDictionary)PmlJsonReader.DecodeMessage(response_string); PmlCollection challenges = (PmlCollection)response["challenges"]; PmlDictionary challenge = null; foreach (PmlDictionary item in challenges) { if ((String)item["type"] == "http-01") { challenge = item; } } String challenge_token = (String)challenge["token"]; String challenge_uri = (String)challenge["uri"]; String keyauth = challenge_token + "." + thumbprint; challenge_callback("/.well-known/acme-challenge/" + challenge_token, keyauth); try { response_string = signed_request(challenge_uri, new PmlDictionary() { { "resource", "challenge" }, { "keyAuthorization", keyauth } }); response = (PmlDictionary)PmlJsonReader.DecodeMessage(response_string); while ((String)response["status"] == "pending") { Thread.Sleep(1000); using (WebClient wc = new WebClient()) response_string = wc.DownloadData(challenge_uri); response = (PmlDictionary)PmlJsonReader.DecodeMessage(response_string); } } finally { challenge_callback("/.well-known/acme-challenge/" + challenge_token, null); } if ((String)response["status"] != "valid") { throw new InvalidOperationException("Challenge rejected for domain " + altname + " (" + response["status"] + ")"); } } }