forked from TrueCloudLab/lego
126 lines
3.1 KiB
Go
126 lines
3.1 KiB
Go
|
package nns01
|
||
|
|
||
|
import (
|
||
|
"crypto/sha256"
|
||
|
"encoding/base64"
|
||
|
"fmt"
|
||
|
"time"
|
||
|
|
||
|
"github.com/go-acme/lego/v4/acme"
|
||
|
"github.com/go-acme/lego/v4/acme/api"
|
||
|
"github.com/go-acme/lego/v4/challenge"
|
||
|
"github.com/go-acme/lego/v4/log"
|
||
|
)
|
||
|
|
||
|
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
|
||
|
|
||
|
// Challenge implements the nns-01 challenge.
|
||
|
type Challenge struct {
|
||
|
core *api.Core
|
||
|
validate ValidateFunc
|
||
|
provider challenge.Provider
|
||
|
}
|
||
|
|
||
|
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider) *Challenge {
|
||
|
chlg := &Challenge{
|
||
|
core: core,
|
||
|
validate: validate,
|
||
|
provider: provider,
|
||
|
}
|
||
|
|
||
|
return chlg
|
||
|
}
|
||
|
|
||
|
// PreSolve submits the txt record to the nns provider.
|
||
|
func (c *Challenge) PreSolve(authz acme.Authorization) error {
|
||
|
domain := challenge.GetTargetedDomain(authz)
|
||
|
log.Infof("[%s] acme: Preparing to solve NNS-01", domain)
|
||
|
|
||
|
chlng, keyAuth, err := c.getChallengeInfo(authz)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
err = c.provider.Present(authz.Identifier.Value, chlng.Token, keyAuth)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("[%s] acme: error presenting token: %w", domain, err)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *Challenge) Solve(authz acme.Authorization) error {
|
||
|
domain := challenge.GetTargetedDomain(authz)
|
||
|
log.Infof("[%s] acme: Trying to solve NNS-01", domain)
|
||
|
|
||
|
chlng, keyAuth, err := c.getChallengeInfo(authz)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
chlng.KeyAuthorization = keyAuth
|
||
|
return c.validate(c.core, domain, chlng)
|
||
|
}
|
||
|
|
||
|
// CleanUp cleans the challenge.
|
||
|
func (c *Challenge) CleanUp(authz acme.Authorization) error {
|
||
|
log.Infof("[%s] acme: Cleaning NNS-01 challenge", challenge.GetTargetedDomain(authz))
|
||
|
|
||
|
chlng, keyAuth, err := c.getChallengeInfo(authz)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return c.provider.CleanUp(authz.Identifier.Value, chlng.Token, keyAuth)
|
||
|
}
|
||
|
|
||
|
func (c *Challenge) Sequential() (bool, time.Duration) {
|
||
|
if p, ok := c.provider.(sequential); ok {
|
||
|
return ok, p.Sequential()
|
||
|
}
|
||
|
return false, 0
|
||
|
}
|
||
|
|
||
|
type sequential interface {
|
||
|
Sequential() time.Duration
|
||
|
}
|
||
|
|
||
|
// RecordInfo contains the information use to create the TXT record.
|
||
|
type RecordInfo struct {
|
||
|
// FQDN is the full-qualified challenge domain (`acme-challenge.[domain]`)
|
||
|
FQDN string
|
||
|
// Value contains the value for the TXT record
|
||
|
Value string
|
||
|
}
|
||
|
|
||
|
// GetRecordInfo returns information used to create a TXT record which will fulfill the `nns-01` challenge.
|
||
|
func GetRecordInfo(domain, keyAuth string) RecordInfo {
|
||
|
keyAuthShaBytes := sha256.Sum256([]byte(keyAuth))
|
||
|
// base64URL encoding without padding
|
||
|
value := base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
|
||
|
|
||
|
return RecordInfo{
|
||
|
Value: value,
|
||
|
FQDN: getRecordFQDN(domain),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func getRecordFQDN(domain string) string {
|
||
|
return fmt.Sprintf("acme-challenge.%s", domain)
|
||
|
}
|
||
|
|
||
|
func (c *Challenge) getChallengeInfo(authz acme.Authorization) (acme.Challenge, string, error) {
|
||
|
chlng, err := challenge.FindChallenge(challenge.NNS01, authz)
|
||
|
if err != nil {
|
||
|
return acme.Challenge{}, "", err
|
||
|
}
|
||
|
|
||
|
// Generate the Key Authorization for the challenge
|
||
|
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
|
||
|
if err != nil {
|
||
|
return acme.Challenge{}, "", err
|
||
|
}
|
||
|
|
||
|
return chlng, keyAuth, nil
|
||
|
}
|