aa3c61c85b
- 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.
188 lines
5.2 KiB
Go
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
|
|
}
|