// Copyright 2020 Matthew Holt // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package acme import ( "fmt" "go.uber.org/zap/zapcore" ) // Problem carries the details of an error from HTTP APIs as // defined in RFC 7807: https://tools.ietf.org/html/rfc7807 // and as extended by RFC 8555 §6.7: // https://tools.ietf.org/html/rfc8555#section-6.7 type Problem struct { // "type" (string) - A URI reference [RFC3986] that identifies the // problem type. This specification encourages that, when // dereferenced, it provide human-readable documentation for the // problem type (e.g., using HTML [W3C.REC-html5-20141028]). When // this member is not present, its value is assumed to be // "about:blank". §3.1 Type string `json:"type"` // "title" (string) - A short, human-readable summary of the problem // type. It SHOULD NOT change from occurrence to occurrence of the // problem, except for purposes of localization (e.g., using // proactive content negotiation; see [RFC7231], Section 3.4). §3.1 Title string `json:"title,omitempty"` // "status" (number) - The HTTP status code ([RFC7231], Section 6) // generated by the origin server for this occurrence of the problem. // §3.1 Status int `json:"status,omitempty"` // "detail" (string) - A human-readable explanation specific to this // occurrence of the problem. §3.1 // // RFC 8555 §6.7: "Clients SHOULD display the 'detail' field of all // errors." Detail string `json:"detail,omitempty"` // "instance" (string) - A URI reference that identifies the specific // occurrence of the problem. It may or may not yield further // information if dereferenced. §3.1 Instance string `json:"instance,omitempty"` // "Sometimes a CA may need to return multiple errors in response to a // request. Additionally, the CA may need to attribute errors to // specific identifiers. For instance, a newOrder request may contain // multiple identifiers for which the CA cannot issue certificates. In // this situation, an ACME problem document MAY contain the // 'subproblems' field, containing a JSON array of problem documents." // RFC 8555 §6.7.1 Subproblems []Subproblem `json:"subproblems,omitempty"` // For convenience, we've added this field to associate with a value // that is related to or caused the problem. It is not part of the // spec, but, if a challenge fails for example, we can associate the // error with the problematic authz object by setting this field. // Challenge failures will have this set to an Authorization type. Resource interface{} `json:"-"` } func (p Problem) Error() string { // TODO: 7.3.3: Handle changes to Terms of Service (notice it uses the Instance field and Link header) // RFC 8555 §6.7: "Clients SHOULD display the 'detail' field of all errors." s := fmt.Sprintf("HTTP %d %s - %s", p.Status, p.Type, p.Detail) if len(p.Subproblems) > 0 { for _, v := range p.Subproblems { s += fmt.Sprintf(", problem %q: %s", v.Type, v.Detail) if v.Identifier.Type != "" || v.Identifier.Value != "" { s += fmt.Sprintf(" (%s_identifier=%s)", v.Identifier.Type, v.Identifier.Value) } } } if p.Instance != "" { s += ", url: " + p.Instance } return s } // MarshalLogObject satisfies the zapcore.ObjectMarshaler interface. // This allows problems to be serialized by the zap logger. func (p Problem) MarshalLogObject(enc zapcore.ObjectEncoder) error { enc.AddString("type", p.Type) enc.AddString("title", p.Title) enc.AddString("detail", p.Detail) enc.AddString("instance", p.Instance) enc.AddArray("subproblems", loggableSubproblems(p.Subproblems)) return nil } // Subproblem describes a more specific error in a problem according to // RFC 8555 §6.7.1: "An ACME problem document MAY contain the // 'subproblems' field, containing a JSON array of problem documents, // each of which MAY contain an 'identifier' field." type Subproblem struct { Problem // "If present, the 'identifier' field MUST contain an ACME // identifier (Section 9.7.7)." §6.7.1 Identifier Identifier `json:"identifier,omitempty"` } // MarshalLogObject satisfies the zapcore.ObjectMarshaler interface. // This allows subproblems to be serialized by the zap logger. func (sp Subproblem) MarshalLogObject(enc zapcore.ObjectEncoder) error { enc.AddString("identifier_type", sp.Identifier.Type) enc.AddString("identifier", sp.Identifier.Value) enc.AddObject("subproblem", sp.Problem) return nil } type loggableSubproblems []Subproblem // MarshalLogArray satisfies the zapcore.ArrayMarshaler interface. // This allows a list of subproblems to be serialized by the zap logger. func (ls loggableSubproblems) MarshalLogArray(enc zapcore.ArrayEncoder) error { for _, sp := range ls { enc.AppendObject(sp) } return nil } // Standard token values for the "type" field of problems, as defined // in RFC 8555 §6.7: https://tools.ietf.org/html/rfc8555#section-6.7 // // "To facilitate automatic response to errors, this document defines the // following standard tokens for use in the 'type' field (within the // ACME URN namespace 'urn:ietf:params:acme:error:') ... This list is not // exhaustive. The server MAY return errors whose 'type' field is set to // a URI other than those defined above." const ( // The ACME error URN prefix. ProblemTypeNamespace = "urn:ietf:params:acme:error:" ProblemTypeAccountDoesNotExist = ProblemTypeNamespace + "accountDoesNotExist" ProblemTypeAlreadyRevoked = ProblemTypeNamespace + "alreadyRevoked" ProblemTypeBadCSR = ProblemTypeNamespace + "badCSR" ProblemTypeBadNonce = ProblemTypeNamespace + "badNonce" ProblemTypeBadPublicKey = ProblemTypeNamespace + "badPublicKey" ProblemTypeBadRevocationReason = ProblemTypeNamespace + "badRevocationReason" ProblemTypeBadSignatureAlgorithm = ProblemTypeNamespace + "badSignatureAlgorithm" ProblemTypeCAA = ProblemTypeNamespace + "caa" ProblemTypeCompound = ProblemTypeNamespace + "compound" ProblemTypeConnection = ProblemTypeNamespace + "connection" ProblemTypeDNS = ProblemTypeNamespace + "dns" ProblemTypeExternalAccountRequired = ProblemTypeNamespace + "externalAccountRequired" ProblemTypeIncorrectResponse = ProblemTypeNamespace + "incorrectResponse" ProblemTypeInvalidContact = ProblemTypeNamespace + "invalidContact" ProblemTypeMalformed = ProblemTypeNamespace + "malformed" ProblemTypeOrderNotReady = ProblemTypeNamespace + "orderNotReady" ProblemTypeRateLimited = ProblemTypeNamespace + "rateLimited" ProblemTypeRejectedIdentifier = ProblemTypeNamespace + "rejectedIdentifier" ProblemTypeServerInternal = ProblemTypeNamespace + "serverInternal" ProblemTypeTLS = ProblemTypeNamespace + "tls" ProblemTypeUnauthorized = ProblemTypeNamespace + "unauthorized" ProblemTypeUnsupportedContact = ProblemTypeNamespace + "unsupportedContact" ProblemTypeUnsupportedIdentifier = ProblemTypeNamespace + "unsupportedIdentifier" ProblemTypeUserActionRequired = ProblemTypeNamespace + "userActionRequired" )