github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/tls/resource_certificate.go (about)

     1  package tls
     2  
     3  import (
     4  	"crypto"
     5  	"crypto/ecdsa"
     6  	"crypto/elliptic"
     7  	"crypto/rand"
     8  	"crypto/rsa"
     9  	"crypto/sha1"
    10  	"crypto/x509"
    11  	"encoding/asn1"
    12  	"encoding/pem"
    13  	"errors"
    14  	"fmt"
    15  	"math/big"
    16  	"time"
    17  
    18  	"github.com/hashicorp/terraform/helper/schema"
    19  )
    20  
    21  const pemCertType = "CERTIFICATE"
    22  
    23  var keyUsages map[string]x509.KeyUsage = map[string]x509.KeyUsage{
    24  	"digital_signature":  x509.KeyUsageDigitalSignature,
    25  	"content_commitment": x509.KeyUsageContentCommitment,
    26  	"key_encipherment":   x509.KeyUsageKeyEncipherment,
    27  	"data_encipherment":  x509.KeyUsageDataEncipherment,
    28  	"key_agreement":      x509.KeyUsageKeyAgreement,
    29  	"cert_signing":       x509.KeyUsageCertSign,
    30  	"crl_signing":        x509.KeyUsageCRLSign,
    31  	"encipher_only":      x509.KeyUsageEncipherOnly,
    32  	"decipher_only":      x509.KeyUsageDecipherOnly,
    33  }
    34  
    35  var extKeyUsages map[string]x509.ExtKeyUsage = map[string]x509.ExtKeyUsage{
    36  	"any_extended":                  x509.ExtKeyUsageAny,
    37  	"server_auth":                   x509.ExtKeyUsageServerAuth,
    38  	"client_auth":                   x509.ExtKeyUsageClientAuth,
    39  	"code_signing":                  x509.ExtKeyUsageCodeSigning,
    40  	"email_protection":              x509.ExtKeyUsageEmailProtection,
    41  	"ipsec_end_system":              x509.ExtKeyUsageIPSECEndSystem,
    42  	"ipsec_tunnel":                  x509.ExtKeyUsageIPSECTunnel,
    43  	"ipsec_user":                    x509.ExtKeyUsageIPSECUser,
    44  	"timestamping":                  x509.ExtKeyUsageTimeStamping,
    45  	"ocsp_signing":                  x509.ExtKeyUsageOCSPSigning,
    46  	"microsoft_server_gated_crypto": x509.ExtKeyUsageMicrosoftServerGatedCrypto,
    47  	"netscape_server_gated_crypto":  x509.ExtKeyUsageNetscapeServerGatedCrypto,
    48  }
    49  
    50  // rsaPublicKey reflects the ASN.1 structure of a PKCS#1 public key.
    51  type rsaPublicKey struct {
    52  	N *big.Int
    53  	E int
    54  }
    55  
    56  // generateSubjectKeyID generates a SHA-1 hash of the subject public key.
    57  func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) {
    58  	var publicKeyBytes []byte
    59  	var err error
    60  
    61  	switch pub := pub.(type) {
    62  	case *rsa.PublicKey:
    63  		publicKeyBytes, err = asn1.Marshal(rsaPublicKey{N: pub.N, E: pub.E})
    64  		if err != nil {
    65  			return nil, err
    66  		}
    67  	case *ecdsa.PublicKey:
    68  		publicKeyBytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y)
    69  	default:
    70  		return nil, errors.New("only RSA and ECDSA public keys supported")
    71  	}
    72  
    73  	hash := sha1.Sum(publicKeyBytes)
    74  	return hash[:], nil
    75  }
    76  
    77  func resourceCertificateCommonSchema() map[string]*schema.Schema {
    78  	return map[string]*schema.Schema{
    79  		"validity_period_hours": &schema.Schema{
    80  			Type:        schema.TypeInt,
    81  			Required:    true,
    82  			Description: "Number of hours that the certificate will remain valid for",
    83  			ForceNew:    true,
    84  		},
    85  
    86  		"early_renewal_hours": &schema.Schema{
    87  			Type:        schema.TypeInt,
    88  			Optional:    true,
    89  			Default:     0,
    90  			Description: "Number of hours before the certificates expiry when a new certificate will be generated",
    91  			ForceNew:    true,
    92  		},
    93  
    94  		"is_ca_certificate": &schema.Schema{
    95  			Type:        schema.TypeBool,
    96  			Optional:    true,
    97  			Description: "Whether the generated certificate will be usable as a CA certificate",
    98  			ForceNew:    true,
    99  		},
   100  
   101  		"allowed_uses": &schema.Schema{
   102  			Type:        schema.TypeList,
   103  			Required:    true,
   104  			Description: "Uses that are allowed for the certificate",
   105  			ForceNew:    true,
   106  			Elem: &schema.Schema{
   107  				Type: schema.TypeString,
   108  			},
   109  		},
   110  
   111  		"cert_pem": &schema.Schema{
   112  			Type:     schema.TypeString,
   113  			Computed: true,
   114  		},
   115  
   116  		"validity_start_time": &schema.Schema{
   117  			Type:     schema.TypeString,
   118  			Computed: true,
   119  		},
   120  
   121  		"validity_end_time": &schema.Schema{
   122  			Type:     schema.TypeString,
   123  			Computed: true,
   124  		},
   125  	}
   126  }
   127  
   128  func createCertificate(d *schema.ResourceData, template, parent *x509.Certificate, pub crypto.PublicKey, priv interface{}) error {
   129  	var err error
   130  
   131  	template.NotBefore = time.Now()
   132  	template.NotAfter = template.NotBefore.Add(time.Duration(d.Get("validity_period_hours").(int)) * time.Hour)
   133  
   134  	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
   135  	template.SerialNumber, err = rand.Int(rand.Reader, serialNumberLimit)
   136  	if err != nil {
   137  		return fmt.Errorf("failed to generate serial number: %s", err)
   138  	}
   139  
   140  	keyUsesI := d.Get("allowed_uses").([]interface{})
   141  	for _, keyUseI := range keyUsesI {
   142  		keyUse := keyUseI.(string)
   143  		if usage, ok := keyUsages[keyUse]; ok {
   144  			template.KeyUsage |= usage
   145  		}
   146  		if usage, ok := extKeyUsages[keyUse]; ok {
   147  			template.ExtKeyUsage = append(template.ExtKeyUsage, usage)
   148  		}
   149  	}
   150  
   151  	if d.Get("is_ca_certificate").(bool) {
   152  		template.IsCA = true
   153  
   154  		template.SubjectKeyId, err = generateSubjectKeyID(pub)
   155  		if err != nil {
   156  			return fmt.Errorf("failed to set subject key identifier: %s", err)
   157  		}
   158  	}
   159  
   160  	certBytes, err := x509.CreateCertificate(rand.Reader, template, parent, pub, priv)
   161  	if err != nil {
   162  		return fmt.Errorf("error creating certificate: %s", err)
   163  	}
   164  	certPem := string(pem.EncodeToMemory(&pem.Block{Type: pemCertType, Bytes: certBytes}))
   165  
   166  	validFromBytes, err := template.NotBefore.MarshalText()
   167  	if err != nil {
   168  		return fmt.Errorf("error serializing validity_start_time: %s", err)
   169  	}
   170  	validToBytes, err := template.NotAfter.MarshalText()
   171  	if err != nil {
   172  		return fmt.Errorf("error serializing validity_end_time: %s", err)
   173  	}
   174  
   175  	d.SetId(template.SerialNumber.String())
   176  	d.Set("cert_pem", certPem)
   177  	d.Set("validity_start_time", string(validFromBytes))
   178  	d.Set("validity_end_time", string(validToBytes))
   179  
   180  	return nil
   181  }
   182  
   183  func DeleteCertificate(d *schema.ResourceData, meta interface{}) error {
   184  	d.SetId("")
   185  	return nil
   186  }
   187  
   188  func ReadCertificate(d *schema.ResourceData, meta interface{}) error {
   189  
   190  	endTimeStr := d.Get("validity_end_time").(string)
   191  	endTime := time.Now()
   192  	err := endTime.UnmarshalText([]byte(endTimeStr))
   193  	if err != nil {
   194  		// If end time is invalid then we'll just throw away the whole
   195  		// thing so we can generate a new one.
   196  		d.SetId("")
   197  		return nil
   198  	}
   199  
   200  	earlyRenewalPeriod := time.Duration(-d.Get("early_renewal_hours").(int)) * time.Hour
   201  	endTime = endTime.Add(earlyRenewalPeriod)
   202  
   203  	if time.Now().After(endTime) {
   204  		// Treat an expired certificate as not existing, so we'll generate
   205  		// a new one with the next plan.
   206  		d.SetId("")
   207  	}
   208  
   209  	return nil
   210  }