コード例 #1
0
        T ExtractPayload <T>(JwsSignedPayload signedPayload)
        {
            var payloadBytes = CryptoHelper.Base64.UrlDecode(signedPayload.Payload);
            var payloadJson  = CryptoHelper.Base64.UrlDecodeToString(signedPayload.Payload);

            return(JsonConvert.DeserializeObject <T>(payloadJson));
        }
コード例 #2
0
        public ActionResult <Account> NewAccount([FromBody] JwsSignedPayload signedPayload)
        {
            var ph     = ExtractProtectedHeader(signedPayload);
            var jwkSer = JsonConvert.SerializeObject(ph.Jwk);

            ValidateNonce(ph);

            var requ = ExtractPayload <CreateAccountRequest>(signedPayload);

            // We start by saving an empty acct in order to compute the next ID
            var dbAcct = new DbAccount();

            _repo.SaveAccount(dbAcct);

            // Then compute the acct-specific URL based on the assigned ID
            // Sample Kid: https://acme-staging-v02.api.letsencrypt.org/acme/acct/6484231
            var acctId = dbAcct.Id.ToString();
            //var kid = ComputeRelativeUrl($"acct/{acctId}").ToString();
            var kid = Url.Action(nameof(GetAccount), new { acctId });

            // Then we actually fill out the details
            dbAcct.Details = new AccountDetails
            {
                Kid     = kid,
                Payload = new Account
                {
                    Id      = acctId,
                    Key     = ph.Jwk,
                    Contact = requ.Contact?.ToArray(),
                    Status  = "testing",
                    TermsOfServiceAgreed = true,
                    InitialIp            = HttpContext.Connection.RemoteIpAddress?.ToString(),
                    CreatedAt            = DateTime.Now.ToString(),
                }
            };
            dbAcct.Jwk = jwkSer;
            _repo.SaveAccount(dbAcct);

            GenerateNonce();
            Response.Headers.Add(
                "Location",
                dbAcct.Details.Kid);

            return(dbAcct.Details.Payload);
        }
コード例 #3
0
        public ActionResult <Authorization> GetAuthorizationPost(string authzKey, [FromBody] JwsSignedPayload signedPayload)
        {
            var ph = ExtractProtectedHeader(signedPayload);

            ValidateNonce(ph);
            var acct = _repo.GetAccountByKid(ph.Kid);

            if (acct == null)
            {
                throw new Exception("could not resolve account");
            }
            ValidateAccount(acct, signedPayload);
            var requ = ExtractPayload <string>(signedPayload);

            if (requ != null)
            {
                return(NotFound());
            }
            GenerateNonce();
            return(GetAuthorization(authzKey));
        }
コード例 #4
0
        public ActionResult <bool> Revoke(string acctId,
                                          [FromBody] JwsSignedPayload signedPayload)
        {
            var ph = ExtractProtectedHeader(signedPayload);

            ValidateNonce(ph);

            var requ = ExtractPayload <RevokeCertificateRequest>(signedPayload);

            var acct = _repo.GetAccountByKid(ph.Kid);

            if (acct == null)
            {
                throw new Exception("could not resolve account");
            }

            ValidateAccount(acct, signedPayload);

            var derEncodedCertificate = CryptoHelper.Base64.UrlDecode(requ.Certificate);
            var xcrt = new X509Certificate2(derEncodedCertificate);

            var dbCert = _repo.GetCertificateByNative(derEncodedCertificate);

            if (dbCert == null)
            {
                return(NotFound());
            }

            if (dbCert.RevokedReason != null)
            {
                throw new Exception("certificate already revoked");
            }

            dbCert.RevokedReason = requ.Reason;
            _repo.SaveCertificate(dbCert);

            GenerateNonce();
            return(true);
        }
コード例 #5
0
        void ValidateAccount(DbAccount acct, JwsSignedPayload signedPayload)
        {
            var ph  = ExtractProtectedHeader(signedPayload);
            var jwk = JsonConvert.DeserializeObject <Dictionary <string, string> >(acct.Jwk);

            if (string.IsNullOrEmpty(ph.Alg))
            {
                throw new Exception("invalid JWS header, missing 'alg'");
            }
            if (string.IsNullOrEmpty(ph.Url))
            {
                throw new Exception("invalid JWS header, missing 'url'");
            }
            if (string.IsNullOrEmpty(ph.Nonce))
            {
                throw new Exception("invalid JWS header, missing 'nonce'");
            }

            IJwsTool tool = null;

            switch (ph.Alg)
            {
            case "RS256":
                tool = new RSJwsTool {
                    HashSize = 256
                };
                ((RSJwsTool)tool).ImportJwk(acct.Jwk);
                break;

            case "RS384":
                tool = new RSJwsTool {
                    HashSize = 384
                };
                ((RSJwsTool)tool).ImportJwk(acct.Jwk);
                break;

            case "RS512":
                tool = new RSJwsTool {
                    HashSize = 512
                };
                ((RSJwsTool)tool).ImportJwk(acct.Jwk);
                break;

            case "ES256":
                tool = new ESJwsTool {
                    HashSize = 256
                };
                break;

            case "ES384":
                tool = new ESJwsTool {
                    HashSize = 384
                };
                break;

            case "ES512":
                tool = new ESJwsTool {
                    HashSize = 512
                };
                break;

            default:
                throw new Exception("unknown or unsupported signature algorithm");
            }

            var sig = CryptoHelper.Base64.UrlDecode(signedPayload.Signature);
            var pld = CryptoHelper.Base64.UrlDecode(signedPayload.Payload);
            var prt = CryptoHelper.Base64.UrlDecode(signedPayload.Protected);

            var sigInput      = $"{signedPayload.Protected}.{signedPayload.Payload}";
            var sigInputBytes = Encoding.ASCII.GetBytes(sigInput);

            if (!tool.Verify(sigInputBytes, sig))
            {
                throw new Exception("account signature failure");
            }
        }
コード例 #6
0
        void ValidateNonce(JwsSignedPayload signedPayload)
        {
            var protectedHeader = ExtractProtectedHeader(signedPayload);

            ValidateNonce(protectedHeader);
        }
コード例 #7
0
        ProtectedHeader ExtractProtectedHeader(JwsSignedPayload signedPayload)
        {
            var protectedJson = CryptoHelper.Base64.UrlDecodeToString(signedPayload.Protected);

            return(JsonConvert.DeserializeObject <ProtectedHeader>(protectedJson));
        }
コード例 #8
0
        public ActionResult <Challenge> AnswerChallenge(string authzKey, string challengeId,
                                                        [FromBody] JwsSignedPayload signedPayload)
        {
            var ph = ExtractProtectedHeader(signedPayload);

            ValidateNonce(ph);

            var acct = _repo.GetAccountByKid(ph.Kid);

            if (acct == null)
            {
                throw new Exception("could not resolve account");
            }

            ValidateAccount(acct, signedPayload);

            var chlngUrl = Request.GetEncodedUrl();
            var dbChlng  = _repo.GetChallengeByUrl(chlngUrl);

            if (dbChlng == null)
            {
                return(NotFound());
            }
            var dbAuthz = _repo.GetAuthorization(dbChlng.AuthorizationId);

            if (dbAuthz == null)
            {
                return(NotFound());
            }
            var dbOrder = _repo.GetOrder(dbAuthz.OrderId);

            if (dbOrder == null)
            {
                return(NotFound());
            }

            if (acct.Id != dbOrder.AccountId)
            {
                throw new Exception("inconsistent state -- "
                                    + "Challenge Order does not belong to resolved Account");
            }

            if (dbChlng.Payload.Status != "pending")
            {
                throw new Exception("Challenge no longer pending");
            }

            string answer;

            if (dbChlng.Payload.Type == "dns-01")
            {
                // dns-01 Challenge type takes no answer input
                var requ = ExtractPayload <object>(signedPayload);
                answer = "dns-01";
            }
            else if (dbChlng.Payload.Type == "http-01")
            {
                // http-01 Challenge type takes no answer input
                var requ = ExtractPayload <object>(signedPayload);
                answer = "http-01";
            }
            else
            {
                throw new Exception("unsupported Challenge type: " + dbChlng.Payload.Type);
            }

            dbChlng.Payload.Status           = "valid";
            dbChlng.Payload.Validated        = DateTime.Now.ToUniversalTime().ToString();
            dbChlng.Payload.ValidationRecord = new object[]
            {
                new { iTakeYourWordForIt = answer }
            };
            _repo.SaveChallenge(dbChlng);

            if (dbAuthz.Payload.Status == "pending")
            {
                dbAuthz.Payload.Status = "valid";
                _repo.SaveAuthorization(dbAuthz);
            }

            GenerateNonce();

            return(dbChlng.Payload);
        }
コード例 #9
0
        public ActionResult <Order> FinalizeOrder(string acctId, string orderId,
                                                  [FromBody] JwsSignedPayload signedPayload)
        {
            if (!int.TryParse(acctId, out var acctIdNum))
            {
                return(NotFound());
            }
            if (!int.TryParse(orderId, out var orderIdNum))
            {
                return(NotFound());
            }

            var ph = ExtractProtectedHeader(signedPayload);

            ValidateNonce(ph);

            var acct = _repo.GetAccountByKid(ph.Kid);

            if (acct == null)
            {
                throw new Exception("could not resolve account");
            }

            ValidateAccount(acct, signedPayload);

            var dbOrder = _repo.GetOrder(orderIdNum);

            if (dbOrder == null || dbOrder.AccountId != acctIdNum)
            {
                return(NotFound());
            }

            if (acct.Id != dbOrder.AccountId)
            {
                throw new Exception("inconsistent state -- "
                                    + "Challenge Order does not belong to resolved Account");
            }

            if (dbOrder.Details.Payload.Status != "pending")
            {
                throw new Exception("Order no longer pending");
            }

            var requ       = ExtractPayload <FinalizeOrderRequest>(signedPayload);
            var encodedCsr = CryptoHelper.Base64.UrlDecode(requ.Csr);

            var crt = _ca.Sign(PkiEncodingFormat.Der, encodedCsr, PkiHashAlgorithm.Sha256);

            byte[] crtBytes;
            using (var ms = new MemoryStream())
            {
                crt.Save(ms);
                ms.Flush();
                ms.Position = 0;
                crtBytes    = ms.ToArray();
            }

            var certKey = Guid.NewGuid().ToString();
            var certPem = Encoding.UTF8.GetString(crt.Export(PkiEncodingFormat.Pem))
                          + ResolveCaCertPem();
            var dbCert = new DbCertificate
            {
                OrderId = dbOrder.Id,
                CertKey = certKey,
                Native  = crtBytes,
                Pem     = certPem,
            };

            _repo.SaveCertificate(dbCert);

            dbOrder.Details.Payload.Status      = "valid";
            dbOrder.Details.Payload.Certificate = Url.Action(nameof(GetCertificate),
                                                             controller: null, values: new { certKey }, protocol: Request.Scheme);
            _repo.SaveOrder(dbOrder);

            GenerateNonce();

            return(dbOrder.Details.Payload);
        }
コード例 #10
0
        public ActionResult <Order> NewOrder([FromBody] JwsSignedPayload signedPayload)
        {
            var ph = ExtractProtectedHeader(signedPayload);

            ValidateNonce(ph);

            var requ = ExtractPayload <CreateOrderRequest>(signedPayload);

            var acct = _repo.GetAccountByKid(ph.Kid);

            if (acct == null)
            {
                throw new Exception("could not resolve account");
            }
            var acctId = acct.Id.ToString();

            ValidateAccount(acct, signedPayload);

            if (requ.Identifiers.Length == 0)
            {
                throw new Exception("at least one identifier is required");
            }

            if (requ.Identifiers.Length > 100)
            {
                throw new Exception("too many identifiers");
            }

            if (requ.Identifiers.Count(x => x.Type != "dns") > 0)
            {
                throw new Exception("unsupported identifier type");
            }

            // We start by saving an empty order so we can compute the next ID
            var dbOrder = new DbOrder();

            _repo.SaveOrder(dbOrder);

            var orderId  = dbOrder.Id.ToString();
            var orderIds = new { acctId, orderId };
            var orderUrl = Url.Action(nameof(GetOrder), controller: null,
                                      values: orderIds, protocol: Request.Scheme);
            var finalizeUrl = Url.Action(nameof(FinalizeOrder), controller: null,
                                         values: orderIds, protocol: Request.Scheme);

            var authzs = new List <DbAuthorization>();

            foreach (var dnsId in requ.Identifiers)
            {
                var authzKey = Guid.NewGuid().ToString();
                var chlngs   = new List <DbChallenge>();

                var chlngTypes = ChallengeTypes;
                var isWildcard = dnsId.Value.StartsWith("*.");

                if (isWildcard)
                {
                    chlngTypes = ChallengeTypesForWildcard;
                }

                foreach (var chlngType in chlngTypes)
                {
                    var chlngToken = Guid.NewGuid().ToString();

                    // We start by saving an empty challenge so we can compute the next ID
                    var chlng = new DbChallenge
                    {
                        Payload = new Challenge
                        {
                            Token = chlngToken,
                            // We temporarily assign the token to the URL in order
                            // to satisfy the unique constraint on the URL index
                            Url = chlngToken,
                        }
                    };
                    _repo.SaveChallenge(chlng);

                    chlng.Payload = new Challenge
                    {
                        Type   = chlngType,
                        Token  = chlngToken,
                        Status = "pending",
                        Url    = Url.Action(nameof(GetChallenge), controller: null,
                                            values: new { authzKey, challengeId = chlng.Id.ToString() },
                                            protocol: Request.Scheme),
                    };
                    _repo.SaveChallenge(chlng);
                    chlngs.Add(chlng);
                }

                var dbAuthz = new DbAuthorization
                {
                    OrderId = dbOrder.Id,
                    Url     = Url.Action(nameof(GetAuthorization), controller: null,
                                         values: new { authzKey }, protocol: Request.Scheme),
                    Payload = new Authorization
                    {
                        Identifier = dnsId,
                        Status     = "pending",
                        Expires    = DateTime.Now.AddHours(24).ToUniversalTime().ToString(),
                        Challenges = chlngs.Select(x => x.Payload).ToArray(),
                        Wildcard   = isWildcard ? (bool?)true : null,
                    }
                };
                _repo.SaveAuthorization(dbAuthz);
                authzs.Add(dbAuthz);

                foreach (var chlng in chlngs)
                {
                    chlng.AuthorizationId = dbAuthz.Id;
                    _repo.SaveChallenge(chlng);
                }
            }

            dbOrder.Url       = orderUrl;
            dbOrder.AccountId = acct.Id;
            dbOrder.Details   = new OrderDetails
            {
                OrderUrl = orderUrl,
                Payload  = new Order
                {
                    Expires        = DateTime.Now.AddHours(24).ToUniversalTime().ToString(),
                    NotBefore      = null, // requ.NotBefore,
                    NotAfter       = null, //requ.NotAfter,
                    Identifiers    = requ.Identifiers,
                    Authorizations = authzs.Select(x => x.Url).ToArray(),
                    Finalize       = finalizeUrl,
                    Status         = "pending",
                    Error          = null,
                    Certificate    = null,
                }
            };
            _repo.SaveOrder(dbOrder);

            GenerateNonce();

            return(Created(orderUrl, dbOrder.Details.Payload));
        }