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 }