tls_alpn_challenge.go raw

   1  package tlsalpn01
   2  
   3  import (
   4  	"crypto/rsa"
   5  	"crypto/sha256"
   6  	"crypto/tls"
   7  	"crypto/x509/pkix"
   8  	"encoding/asn1"
   9  	"fmt"
  10  	"time"
  11  
  12  	"github.com/go-acme/lego/v4/acme"
  13  	"github.com/go-acme/lego/v4/acme/api"
  14  	"github.com/go-acme/lego/v4/certcrypto"
  15  	"github.com/go-acme/lego/v4/challenge"
  16  	"github.com/go-acme/lego/v4/log"
  17  )
  18  
  19  // idPeAcmeIdentifierV1 is the SMI Security for PKIX Certification Extension OID referencing the ACME extension.
  20  // Reference: https://www.rfc-editor.org/rfc/rfc8737.html#section-6.1
  21  var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
  22  
  23  type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
  24  
  25  type ChallengeOption func(*Challenge) error
  26  
  27  // SetDelay sets a delay between the start of the TLS listener and the challenge validation.
  28  func SetDelay(delay time.Duration) ChallengeOption {
  29  	return func(chlg *Challenge) error {
  30  		chlg.delay = delay
  31  		return nil
  32  	}
  33  }
  34  
  35  type Challenge struct {
  36  	core     *api.Core
  37  	validate ValidateFunc
  38  	provider challenge.Provider
  39  	delay    time.Duration
  40  }
  41  
  42  func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider, opts ...ChallengeOption) *Challenge {
  43  	chlg := &Challenge{
  44  		core:     core,
  45  		validate: validate,
  46  		provider: provider,
  47  	}
  48  
  49  	for _, opt := range opts {
  50  		err := opt(chlg)
  51  		if err != nil {
  52  			log.Infof("challenge option error: %v", err)
  53  		}
  54  	}
  55  
  56  	return chlg
  57  }
  58  
  59  func (c *Challenge) SetProvider(provider challenge.Provider) {
  60  	c.provider = provider
  61  }
  62  
  63  // Solve manages the provider to validate and solve the challenge.
  64  func (c *Challenge) Solve(authz acme.Authorization) error {
  65  	domain := authz.Identifier.Value
  66  	log.Infof("[%s] acme: Trying to solve TLS-ALPN-01", challenge.GetTargetedDomain(authz))
  67  
  68  	chlng, err := challenge.FindChallenge(challenge.TLSALPN01, authz)
  69  	if err != nil {
  70  		return err
  71  	}
  72  
  73  	// Generate the Key Authorization for the challenge
  74  	keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
  75  	if err != nil {
  76  		return err
  77  	}
  78  
  79  	err = c.provider.Present(domain, chlng.Token, keyAuth)
  80  	if err != nil {
  81  		return fmt.Errorf("[%s] acme: error presenting token: %w", challenge.GetTargetedDomain(authz), err)
  82  	}
  83  
  84  	defer func() {
  85  		err := c.provider.CleanUp(domain, chlng.Token, keyAuth)
  86  		if err != nil {
  87  			log.Warnf("[%s] acme: cleaning up failed: %v", challenge.GetTargetedDomain(authz), err)
  88  		}
  89  	}()
  90  
  91  	if c.delay > 0 {
  92  		time.Sleep(c.delay)
  93  	}
  94  
  95  	chlng.KeyAuthorization = keyAuth
  96  
  97  	return c.validate(c.core, domain, chlng)
  98  }
  99  
 100  // ChallengeBlocks returns PEM blocks (certPEMBlock, keyPEMBlock) with the acmeValidation-v1 extension
 101  // and domain name for the `tls-alpn-01` challenge.
 102  func ChallengeBlocks(domain, keyAuth string) ([]byte, []byte, error) {
 103  	// Compute the SHA-256 digest of the key authorization.
 104  	zBytes := sha256.Sum256([]byte(keyAuth))
 105  
 106  	value, err := asn1.Marshal(zBytes[:sha256.Size])
 107  	if err != nil {
 108  		return nil, nil, err
 109  	}
 110  
 111  	// Add the keyAuth digest as the acmeValidation-v1 extension
 112  	// (marked as critical such that it won't be used by non-ACME software).
 113  	// Reference: https://www.rfc-editor.org/rfc/rfc8737.html#section-3
 114  	extensions := []pkix.Extension{
 115  		{
 116  			Id:       idPeAcmeIdentifierV1,
 117  			Critical: true,
 118  			Value:    value,
 119  		},
 120  	}
 121  
 122  	// Generate a new RSA key for the certificates.
 123  	tempPrivateKey, err := certcrypto.GeneratePrivateKey(certcrypto.RSA2048)
 124  	if err != nil {
 125  		return nil, nil, err
 126  	}
 127  
 128  	rsaPrivateKey := tempPrivateKey.(*rsa.PrivateKey)
 129  
 130  	// Generate the PEM certificate using the provided private key, domain, and extra extensions.
 131  	tempCertPEM, err := certcrypto.GeneratePemCert(rsaPrivateKey, domain, extensions)
 132  	if err != nil {
 133  		return nil, nil, err
 134  	}
 135  
 136  	// Encode the private key into a PEM format. We'll need to use it to generate the x509 keypair.
 137  	rsaPrivatePEM := certcrypto.PEMEncode(rsaPrivateKey)
 138  
 139  	return tempCertPEM, rsaPrivatePEM, nil
 140  }
 141  
 142  // ChallengeCert returns a certificate with the acmeValidation-v1 extension
 143  // and domain name for the `tls-alpn-01` challenge.
 144  func ChallengeCert(domain, keyAuth string) (*tls.Certificate, error) {
 145  	tempCertPEM, rsaPrivatePEM, err := ChallengeBlocks(domain, keyAuth)
 146  	if err != nil {
 147  		return nil, err
 148  	}
 149  
 150  	cert, err := tls.X509KeyPair(tempCertPEM, rsaPrivatePEM)
 151  	if err != nil {
 152  		return nil, err
 153  	}
 154  
 155  	return &cert, nil
 156  }
 157