open-vault/builtin/logical/pki/acme_authorizations.go
Steven Clark aa3c61c85b
In ACME responses only return Type, Value fields (#20480)
- Do not serialize the entire internal object, instead return
   just the Type and Value fields back to the caller.
 - Also within authorization responses, return the base domain
   on wildcard queries, dropping the *. as the RFC requests.
 - Update tests to reflect/test this logic.
2023-05-03 09:53:33 -04:00

188 lines
5.2 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package pki
import (
"fmt"
"time"
)
type ACMEIdentifierType string
const (
ACMEDNSIdentifier ACMEIdentifierType = "dns"
ACMEIPIdentifier ACMEIdentifierType = "ip"
)
type ACMEIdentifier struct {
Type ACMEIdentifierType `json:"type"`
Value string `json:"value"`
OriginalValue string `json:"original_value"`
IsWildcard bool `json:"is_wildcard"`
}
func (ai *ACMEIdentifier) MaybeParseWildcard() (bool, string, error) {
if ai.Type != ACMEDNSIdentifier || !isWildcardDomain(ai.Value) {
return false, ai.Value, nil
}
// Here on out, technically it is a wildcard.
ai.IsWildcard = true
wildcardLabel, reducedName, err := validateWildcardDomain(ai.Value)
if err != nil {
return true, "", err
}
if wildcardLabel != "*" {
// Per RFC 8555 Section. 7.1.3. Order Objects:
//
// > Any identifier of type "dns" in a newOrder request MAY have a
// > wildcard domain name as its value. A wildcard domain name consists
// > of a single asterisk character followed by a single full stop
// > character ("*.") followed by a domain name as defined for use in the
// > Subject Alternate Name Extension by [RFC5280].
return true, "", fmt.Errorf("wildcard must be entire left-most label")
}
if reducedName == "" {
return true, "", fmt.Errorf("wildcard must not be entire domain name; need at least two domain labels")
}
// Parsing was indeed successful, so update our reduced name.
ai.Value = reducedName
return true, reducedName, nil
}
func (ai *ACMEIdentifier) NetworkMarshal(useOriginalValue bool) map[string]interface{} {
value := ai.OriginalValue
if !useOriginalValue {
value = ai.Value
}
return map[string]interface{}{
"type": ai.Type,
"value": value,
}
}
type ACMEAuthorizationStatusType string
const (
ACMEAuthorizationPending ACMEAuthorizationStatusType = "pending"
ACMEAuthorizationValid ACMEAuthorizationStatusType = "valid"
ACMEAuthorizationInvalid ACMEAuthorizationStatusType = "invalid"
ACMEAuthorizationDeactivated ACMEAuthorizationStatusType = "deactivated"
ACMEAuthorizationExpired ACMEAuthorizationStatusType = "expired"
ACMEAuthorizationRevoked ACMEAuthorizationStatusType = "revoked"
)
type ACMEOrderStatusType string
const (
ACMEOrderPending ACMEOrderStatusType = "pending"
ACMEOrderProcessing ACMEOrderStatusType = "processing"
ACMEOrderValid ACMEOrderStatusType = "valid"
ACMEOrderInvalid ACMEOrderStatusType = "invalid"
ACMEOrderReady ACMEOrderStatusType = "ready"
)
type ACMEChallengeType string
const (
ACMEHTTPChallenge ACMEChallengeType = "http-01"
ACMEDNSChallenge ACMEChallengeType = "dns-01"
ACMEALPNChallenge ACMEChallengeType = "tls-alpn-01"
)
type ACMEChallengeStatusType string
const (
ACMEChallengePending ACMEChallengeStatusType = "pending"
ACMEChallengeProcessing ACMEChallengeStatusType = "processing"
ACMEChallengeValid ACMEChallengeStatusType = "valid"
ACMEChallengeInvalid ACMEChallengeStatusType = "invalid"
)
type ACMEChallenge struct {
Type ACMEChallengeType `json:"type"`
Status ACMEChallengeStatusType `json:"status"`
Validated string `json:"validated,optional"`
Error map[string]interface{} `json:"error,optional"`
ChallengeFields map[string]interface{} `json:"challenge_fields"`
}
func (ac *ACMEChallenge) NetworkMarshal(acmeCtx *acmeContext, authId string) map[string]interface{} {
resp := map[string]interface{}{
"type": ac.Type,
"url": buildChallengeUrl(acmeCtx, authId, string(ac.Type)),
"status": ac.Status,
}
if ac.Validated != "" {
resp["validated"] = ac.Validated
}
if len(ac.Error) > 0 {
resp["error"] = ac.Error
}
for field, value := range ac.ChallengeFields {
resp[field] = value
}
return resp
}
func buildChallengeUrl(acmeCtx *acmeContext, authId, challengeType string) string {
return acmeCtx.baseUrl.JoinPath("/challenge/", authId, challengeType).String()
}
type ACMEAuthorization struct {
Id string `json:"id"`
AccountId string `json:"account_id"`
Identifier *ACMEIdentifier `json:"identifier"`
Status ACMEAuthorizationStatusType `json:"status"`
// Per RFC 8555 Section 7.1.4. Authorization Objects:
//
// > This field is REQUIRED for objects with "valid" in the "status"
// > field.
Expires string `json:"expires,optional"`
Challenges []*ACMEChallenge `json:"challenges"`
Wildcard bool `json:"wildcard"`
}
func (aa *ACMEAuthorization) GetExpires() (time.Time, error) {
if aa.Expires == "" {
return time.Time{}, nil
}
return time.Parse(time.RFC3339, aa.Expires)
}
func (aa *ACMEAuthorization) NetworkMarshal(acmeCtx *acmeContext) map[string]interface{} {
resp := map[string]interface{}{
"identifier": aa.Identifier.NetworkMarshal( /* use value, not original value */ false),
"status": aa.Status,
"wildcard": aa.Wildcard,
}
if aa.Expires != "" {
resp["expires"] = aa.Expires
}
if len(aa.Challenges) > 0 {
challenges := []map[string]interface{}{}
for _, challenge := range aa.Challenges {
challenges = append(challenges, challenge.NetworkMarshal(acmeCtx, aa.Id))
}
resp["challenges"] = challenges
}
return resp
}