...

Source file src/github.com/letsencrypt/boulder/probs/probs.go

Documentation: github.com/letsencrypt/boulder/probs

     1  package probs
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  
     7  	"github.com/letsencrypt/boulder/identifier"
     8  )
     9  
    10  const (
    11  	// Error types that can be used in ACME payloads. These are sorted in the
    12  	// same order as they are defined in RFC8555 Section 6.7. We do not implement
    13  	// the `compound`, `externalAccountRequired`, or `userActionRequired` errors,
    14  	// because we have no path that would return them.
    15  	AccountDoesNotExistProblem   = ProblemType("accountDoesNotExist")
    16  	AlreadyRevokedProblem        = ProblemType("alreadyRevoked")
    17  	BadCSRProblem                = ProblemType("badCSR")
    18  	BadNonceProblem              = ProblemType("badNonce")
    19  	BadPublicKeyProblem          = ProblemType("badPublicKey")
    20  	BadRevocationReasonProblem   = ProblemType("badRevocationReason")
    21  	BadSignatureAlgorithmProblem = ProblemType("badSignatureAlgorithm")
    22  	CAAProblem                   = ProblemType("caa")
    23  	ConnectionProblem            = ProblemType("connection")
    24  	DNSProblem                   = ProblemType("dns")
    25  	InvalidContactProblem        = ProblemType("invalidContact")
    26  	MalformedProblem             = ProblemType("malformed")
    27  	OrderNotReadyProblem         = ProblemType("orderNotReady")
    28  	RateLimitedProblem           = ProblemType("rateLimited")
    29  	RejectedIdentifierProblem    = ProblemType("rejectedIdentifier")
    30  	ServerInternalProblem        = ProblemType("serverInternal")
    31  	TLSProblem                   = ProblemType("tls")
    32  	UnauthorizedProblem          = ProblemType("unauthorized")
    33  	UnsupportedContactProblem    = ProblemType("unsupportedContact")
    34  	UnsupportedIdentifierProblem = ProblemType("unsupportedIdentifier")
    35  
    36  	ErrorNS = "urn:ietf:params:acme:error:"
    37  )
    38  
    39  // ProblemType defines the error types in the ACME protocol
    40  type ProblemType string
    41  
    42  // ProblemDetails objects represent problem documents
    43  // https://tools.ietf.org/html/draft-ietf-appsawg-http-problem-00
    44  type ProblemDetails struct {
    45  	Type   ProblemType `json:"type,omitempty"`
    46  	Detail string      `json:"detail,omitempty"`
    47  	// HTTPStatus is the HTTP status code the ProblemDetails should probably be sent
    48  	// as.
    49  	HTTPStatus int `json:"status,omitempty"`
    50  	// SubProblems are optional additional per-identifier problems. See
    51  	// RFC 8555 Section 6.7.1: https://tools.ietf.org/html/rfc8555#section-6.7.1
    52  	SubProblems []SubProblemDetails `json:"subproblems,omitempty"`
    53  }
    54  
    55  // SubProblemDetails represents sub-problems specific to an identifier that are
    56  // related to a top-level ProblemDetails.
    57  // See RFC 8555 Section 6.7.1: https://tools.ietf.org/html/rfc8555#section-6.7.1
    58  type SubProblemDetails struct {
    59  	ProblemDetails
    60  	Identifier identifier.ACMEIdentifier `json:"identifier"`
    61  }
    62  
    63  func (pd *ProblemDetails) Error() string {
    64  	return fmt.Sprintf("%s :: %s", pd.Type, pd.Detail)
    65  }
    66  
    67  // WithSubProblems returns a new ProblemsDetails instance created by adding the
    68  // provided subProbs to the existing ProblemsDetail.
    69  func (pd *ProblemDetails) WithSubProblems(subProbs []SubProblemDetails) *ProblemDetails {
    70  	return &ProblemDetails{
    71  		Type:        pd.Type,
    72  		Detail:      pd.Detail,
    73  		HTTPStatus:  pd.HTTPStatus,
    74  		SubProblems: append(pd.SubProblems, subProbs...),
    75  	}
    76  }
    77  
    78  // Helper functions which construct the basic RFC8555 Problem Documents, with
    79  // the Type already set and the Details supplied by the caller.
    80  
    81  // AccountDoesNotExist returns a ProblemDetails representing an
    82  // AccountDoesNotExistProblem error
    83  func AccountDoesNotExist(detail string) *ProblemDetails {
    84  	return &ProblemDetails{
    85  		Type:       AccountDoesNotExistProblem,
    86  		Detail:     detail,
    87  		HTTPStatus: http.StatusBadRequest,
    88  	}
    89  }
    90  
    91  // AlreadyRevoked returns a ProblemDetails with a AlreadyRevokedProblem and a 400 Bad
    92  // Request status code.
    93  func AlreadyRevoked(detail string, a ...any) *ProblemDetails {
    94  	return &ProblemDetails{
    95  		Type:       AlreadyRevokedProblem,
    96  		Detail:     fmt.Sprintf(detail, a...),
    97  		HTTPStatus: http.StatusBadRequest,
    98  	}
    99  }
   100  
   101  // BadCSR returns a ProblemDetails representing a BadCSRProblem.
   102  func BadCSR(detail string, a ...any) *ProblemDetails {
   103  	return &ProblemDetails{
   104  		Type:       BadCSRProblem,
   105  		Detail:     fmt.Sprintf(detail, a...),
   106  		HTTPStatus: http.StatusBadRequest,
   107  	}
   108  }
   109  
   110  // BadNonce returns a ProblemDetails with a BadNonceProblem and a 400 Bad
   111  // Request status code.
   112  func BadNonce(detail string) *ProblemDetails {
   113  	return &ProblemDetails{
   114  		Type:       BadNonceProblem,
   115  		Detail:     detail,
   116  		HTTPStatus: http.StatusBadRequest,
   117  	}
   118  }
   119  
   120  // BadPublicKey returns a ProblemDetails with a BadPublicKeyProblem and a 400 Bad
   121  // Request status code.
   122  func BadPublicKey(detail string, a ...any) *ProblemDetails {
   123  	return &ProblemDetails{
   124  		Type:       BadPublicKeyProblem,
   125  		Detail:     fmt.Sprintf(detail, a...),
   126  		HTTPStatus: http.StatusBadRequest,
   127  	}
   128  }
   129  
   130  // BadRevocationReason returns a ProblemDetails representing
   131  // a BadRevocationReasonProblem
   132  func BadRevocationReason(detail string, a ...any) *ProblemDetails {
   133  	return &ProblemDetails{
   134  		Type:       BadRevocationReasonProblem,
   135  		Detail:     fmt.Sprintf(detail, a...),
   136  		HTTPStatus: http.StatusBadRequest,
   137  	}
   138  }
   139  
   140  // BadSignatureAlgorithm returns a ProblemDetails with a BadSignatureAlgorithmProblem
   141  // and a 400 Bad Request status code.
   142  func BadSignatureAlgorithm(detail string, a ...any) *ProblemDetails {
   143  	return &ProblemDetails{
   144  		Type:       BadSignatureAlgorithmProblem,
   145  		Detail:     fmt.Sprintf(detail, a...),
   146  		HTTPStatus: http.StatusBadRequest,
   147  	}
   148  }
   149  
   150  // CAA returns a ProblemDetails representing a CAAProblem
   151  func CAA(detail string) *ProblemDetails {
   152  	return &ProblemDetails{
   153  		Type:       CAAProblem,
   154  		Detail:     detail,
   155  		HTTPStatus: http.StatusForbidden,
   156  	}
   157  }
   158  
   159  // Connection returns a ProblemDetails representing a ConnectionProblem
   160  // error
   161  func Connection(detail string) *ProblemDetails {
   162  	return &ProblemDetails{
   163  		Type:       ConnectionProblem,
   164  		Detail:     detail,
   165  		HTTPStatus: http.StatusBadRequest,
   166  	}
   167  }
   168  
   169  // DNS returns a ProblemDetails representing a DNSProblem
   170  func DNS(detail string) *ProblemDetails {
   171  	return &ProblemDetails{
   172  		Type:       DNSProblem,
   173  		Detail:     detail,
   174  		HTTPStatus: http.StatusBadRequest,
   175  	}
   176  }
   177  
   178  // InvalidContact returns a ProblemDetails representing an InvalidContactProblem.
   179  func InvalidContact(detail string) *ProblemDetails {
   180  	return &ProblemDetails{
   181  		Type:       InvalidContactProblem,
   182  		Detail:     detail,
   183  		HTTPStatus: http.StatusBadRequest,
   184  	}
   185  }
   186  
   187  // Malformed returns a ProblemDetails with a MalformedProblem and a 400 Bad
   188  // Request status code.
   189  func Malformed(detail string, a ...any) *ProblemDetails {
   190  	if len(a) > 0 {
   191  		detail = fmt.Sprintf(detail, a...)
   192  	}
   193  	return &ProblemDetails{
   194  		Type:       MalformedProblem,
   195  		Detail:     detail,
   196  		HTTPStatus: http.StatusBadRequest,
   197  	}
   198  }
   199  
   200  // OrderNotReady returns a ProblemDetails representing a OrderNotReadyProblem
   201  func OrderNotReady(detail string, a ...any) *ProblemDetails {
   202  	return &ProblemDetails{
   203  		Type:       OrderNotReadyProblem,
   204  		Detail:     fmt.Sprintf(detail, a...),
   205  		HTTPStatus: http.StatusForbidden,
   206  	}
   207  }
   208  
   209  // RateLimited returns a ProblemDetails representing a RateLimitedProblem error
   210  func RateLimited(detail string) *ProblemDetails {
   211  	return &ProblemDetails{
   212  		Type:       RateLimitedProblem,
   213  		Detail:     detail,
   214  		HTTPStatus: http.StatusTooManyRequests,
   215  	}
   216  }
   217  
   218  // RejectedIdentifier returns a ProblemDetails with a RejectedIdentifierProblem and a 400 Bad
   219  // Request status code.
   220  func RejectedIdentifier(detail string) *ProblemDetails {
   221  	return &ProblemDetails{
   222  		Type:       RejectedIdentifierProblem,
   223  		Detail:     detail,
   224  		HTTPStatus: http.StatusBadRequest,
   225  	}
   226  }
   227  
   228  // ServerInternal returns a ProblemDetails with a ServerInternalProblem and a
   229  // 500 Internal Server Failure status code.
   230  func ServerInternal(detail string) *ProblemDetails {
   231  	return &ProblemDetails{
   232  		Type:       ServerInternalProblem,
   233  		Detail:     detail,
   234  		HTTPStatus: http.StatusInternalServerError,
   235  	}
   236  }
   237  
   238  // TLS returns a ProblemDetails representing a TLSProblem error
   239  func TLS(detail string) *ProblemDetails {
   240  	return &ProblemDetails{
   241  		Type:       TLSProblem,
   242  		Detail:     detail,
   243  		HTTPStatus: http.StatusBadRequest,
   244  	}
   245  }
   246  
   247  // Unauthorized returns a ProblemDetails with an UnauthorizedProblem and a 403
   248  // Forbidden status code.
   249  func Unauthorized(detail string) *ProblemDetails {
   250  	return &ProblemDetails{
   251  		Type:       UnauthorizedProblem,
   252  		Detail:     detail,
   253  		HTTPStatus: http.StatusForbidden,
   254  	}
   255  }
   256  
   257  // UnsupportedContact returns a ProblemDetails representing an
   258  // UnsupportedContactProblem
   259  func UnsupportedContact(detail string) *ProblemDetails {
   260  	return &ProblemDetails{
   261  		Type:       UnsupportedContactProblem,
   262  		Detail:     detail,
   263  		HTTPStatus: http.StatusBadRequest,
   264  	}
   265  }
   266  
   267  // UnsupportedIdentifier returns a ProblemDetails representing an
   268  // UnsupportedIdentifierProblem
   269  func UnsupportedIdentifier(detail string, a ...any) *ProblemDetails {
   270  	return &ProblemDetails{
   271  		Type:       UnsupportedIdentifierProblem,
   272  		Detail:     fmt.Sprintf(detail, a...),
   273  		HTTPStatus: http.StatusBadRequest,
   274  	}
   275  }
   276  
   277  // Additional helper functions that return variations on MalformedProblem with
   278  // different HTTP status codes set.
   279  
   280  // Canceled returns a ProblemDetails with a MalformedProblem and a 408 Request
   281  // Timeout status code.
   282  func Canceled(detail string, a ...any) *ProblemDetails {
   283  	if len(a) > 0 {
   284  		detail = fmt.Sprintf(detail, a...)
   285  	}
   286  	return &ProblemDetails{
   287  		Type:       MalformedProblem,
   288  		Detail:     detail,
   289  		HTTPStatus: http.StatusRequestTimeout,
   290  	}
   291  }
   292  
   293  // Conflict returns a ProblemDetails with a MalformedProblem and a 409 Conflict
   294  // status code.
   295  func Conflict(detail string) *ProblemDetails {
   296  	return &ProblemDetails{
   297  		Type:       MalformedProblem,
   298  		Detail:     detail,
   299  		HTTPStatus: http.StatusConflict,
   300  	}
   301  }
   302  
   303  // ContentLengthRequired returns a ProblemDetails representing a missing
   304  // Content-Length header error
   305  func ContentLengthRequired() *ProblemDetails {
   306  	return &ProblemDetails{
   307  		Type:       MalformedProblem,
   308  		Detail:     "missing Content-Length header",
   309  		HTTPStatus: http.StatusLengthRequired,
   310  	}
   311  }
   312  
   313  // InvalidContentType returns a ProblemDetails suitable for a missing
   314  // ContentType header, or an incorrect ContentType header
   315  func InvalidContentType(detail string) *ProblemDetails {
   316  	return &ProblemDetails{
   317  		Type:       MalformedProblem,
   318  		Detail:     detail,
   319  		HTTPStatus: http.StatusUnsupportedMediaType,
   320  	}
   321  }
   322  
   323  // MethodNotAllowed returns a ProblemDetails representing a disallowed HTTP
   324  // method error.
   325  func MethodNotAllowed() *ProblemDetails {
   326  	return &ProblemDetails{
   327  		Type:       MalformedProblem,
   328  		Detail:     "Method not allowed",
   329  		HTTPStatus: http.StatusMethodNotAllowed,
   330  	}
   331  }
   332  
   333  // NotFound returns a ProblemDetails with a MalformedProblem and a 404 Not Found
   334  // status code.
   335  func NotFound(detail string) *ProblemDetails {
   336  	return &ProblemDetails{
   337  		Type:       MalformedProblem,
   338  		Detail:     detail,
   339  		HTTPStatus: http.StatusNotFound,
   340  	}
   341  }
   342  

View as plain text