2015-11-15 17:45:05 +01:00
|
|
|
package tls
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto"
|
|
|
|
"crypto/ecdsa"
|
|
|
|
"crypto/elliptic"
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rsa"
|
|
|
|
"crypto/sha1"
|
|
|
|
"crypto/x509"
|
|
|
|
"encoding/asn1"
|
|
|
|
"encoding/pem"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"math/big"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
|
|
)
|
|
|
|
|
|
|
|
const pemCertType = "CERTIFICATE"
|
|
|
|
|
|
|
|
var keyUsages map[string]x509.KeyUsage = map[string]x509.KeyUsage{
|
|
|
|
"digital_signature": x509.KeyUsageDigitalSignature,
|
|
|
|
"content_commitment": x509.KeyUsageContentCommitment,
|
|
|
|
"key_encipherment": x509.KeyUsageKeyEncipherment,
|
|
|
|
"data_encipherment": x509.KeyUsageDataEncipherment,
|
|
|
|
"key_agreement": x509.KeyUsageKeyAgreement,
|
|
|
|
"cert_signing": x509.KeyUsageCertSign,
|
|
|
|
"crl_signing": x509.KeyUsageCRLSign,
|
|
|
|
"encipher_only": x509.KeyUsageEncipherOnly,
|
|
|
|
"decipher_only": x509.KeyUsageDecipherOnly,
|
|
|
|
}
|
|
|
|
|
|
|
|
var extKeyUsages map[string]x509.ExtKeyUsage = map[string]x509.ExtKeyUsage{
|
|
|
|
"any_extended": x509.ExtKeyUsageAny,
|
|
|
|
"server_auth": x509.ExtKeyUsageServerAuth,
|
|
|
|
"client_auth": x509.ExtKeyUsageClientAuth,
|
|
|
|
"code_signing": x509.ExtKeyUsageCodeSigning,
|
|
|
|
"email_protection": x509.ExtKeyUsageEmailProtection,
|
|
|
|
"ipsec_end_system": x509.ExtKeyUsageIPSECEndSystem,
|
|
|
|
"ipsec_tunnel": x509.ExtKeyUsageIPSECTunnel,
|
|
|
|
"ipsec_user": x509.ExtKeyUsageIPSECUser,
|
|
|
|
"timestamping": x509.ExtKeyUsageTimeStamping,
|
|
|
|
"ocsp_signing": x509.ExtKeyUsageOCSPSigning,
|
|
|
|
"microsoft_server_gated_crypto": x509.ExtKeyUsageMicrosoftServerGatedCrypto,
|
|
|
|
"netscape_server_gated_crypto": x509.ExtKeyUsageNetscapeServerGatedCrypto,
|
|
|
|
}
|
|
|
|
|
|
|
|
// rsaPublicKey reflects the ASN.1 structure of a PKCS#1 public key.
|
|
|
|
type rsaPublicKey struct {
|
|
|
|
N *big.Int
|
|
|
|
E int
|
|
|
|
}
|
|
|
|
|
|
|
|
// generateSubjectKeyID generates a SHA-1 hash of the subject public key.
|
|
|
|
func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) {
|
|
|
|
var publicKeyBytes []byte
|
|
|
|
var err error
|
|
|
|
|
|
|
|
switch pub := pub.(type) {
|
|
|
|
case *rsa.PublicKey:
|
|
|
|
publicKeyBytes, err = asn1.Marshal(rsaPublicKey{N: pub.N, E: pub.E})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
case *ecdsa.PublicKey:
|
|
|
|
publicKeyBytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y)
|
|
|
|
default:
|
|
|
|
return nil, errors.New("only RSA and ECDSA public keys supported")
|
|
|
|
}
|
|
|
|
|
|
|
|
hash := sha1.Sum(publicKeyBytes)
|
|
|
|
return hash[:], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceCertificateCommonSchema() map[string]*schema.Schema {
|
|
|
|
return map[string]*schema.Schema{
|
|
|
|
"validity_period_hours": &schema.Schema{
|
|
|
|
Type: schema.TypeInt,
|
|
|
|
Required: true,
|
|
|
|
Description: "Number of hours that the certificate will remain valid for",
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"early_renewal_hours": &schema.Schema{
|
|
|
|
Type: schema.TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
Default: 0,
|
|
|
|
Description: "Number of hours before the certificates expiry when a new certificate will be generated",
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"is_ca_certificate": &schema.Schema{
|
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
Description: "Whether the generated certificate will be usable as a CA certificate",
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"allowed_uses": &schema.Schema{
|
|
|
|
Type: schema.TypeList,
|
|
|
|
Required: true,
|
|
|
|
Description: "Uses that are allowed for the certificate",
|
|
|
|
ForceNew: true,
|
|
|
|
Elem: &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
"cert_pem": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"validity_start_time": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"validity_end_time": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func createCertificate(d *schema.ResourceData, template, parent *x509.Certificate, pub crypto.PublicKey, priv interface{}) error {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
template.NotBefore = time.Now()
|
|
|
|
template.NotAfter = template.NotBefore.Add(time.Duration(d.Get("validity_period_hours").(int)) * time.Hour)
|
|
|
|
|
|
|
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
|
|
template.SerialNumber, err = rand.Int(rand.Reader, serialNumberLimit)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to generate serial number: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
keyUsesI := d.Get("allowed_uses").([]interface{})
|
|
|
|
for _, keyUseI := range keyUsesI {
|
|
|
|
keyUse := keyUseI.(string)
|
|
|
|
if usage, ok := keyUsages[keyUse]; ok {
|
|
|
|
template.KeyUsage |= usage
|
|
|
|
}
|
|
|
|
if usage, ok := extKeyUsages[keyUse]; ok {
|
|
|
|
template.ExtKeyUsage = append(template.ExtKeyUsage, usage)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if d.Get("is_ca_certificate").(bool) {
|
|
|
|
template.IsCA = true
|
|
|
|
|
|
|
|
template.SubjectKeyId, err = generateSubjectKeyID(pub)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to set subject key identifier: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, template, parent, pub, priv)
|
|
|
|
if err != nil {
|
2016-02-13 07:39:23 +01:00
|
|
|
return fmt.Errorf("error creating certificate: %s", err)
|
2015-11-15 17:45:05 +01:00
|
|
|
}
|
|
|
|
certPem := string(pem.EncodeToMemory(&pem.Block{Type: pemCertType, Bytes: certBytes}))
|
|
|
|
|
|
|
|
validFromBytes, err := template.NotBefore.MarshalText()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error serializing validity_start_time: %s", err)
|
|
|
|
}
|
|
|
|
validToBytes, err := template.NotAfter.MarshalText()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error serializing validity_end_time: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
d.SetId(template.SerialNumber.String())
|
|
|
|
d.Set("cert_pem", certPem)
|
|
|
|
d.Set("validity_start_time", string(validFromBytes))
|
|
|
|
d.Set("validity_end_time", string(validToBytes))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func DeleteCertificate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
d.SetId("")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func ReadCertificate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
|
|
|
|
endTimeStr := d.Get("validity_end_time").(string)
|
|
|
|
endTime := time.Now()
|
|
|
|
err := endTime.UnmarshalText([]byte(endTimeStr))
|
|
|
|
if err != nil {
|
|
|
|
// If end time is invalid then we'll just throw away the whole
|
|
|
|
// thing so we can generate a new one.
|
|
|
|
d.SetId("")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
earlyRenewalPeriod := time.Duration(-d.Get("early_renewal_hours").(int)) * time.Hour
|
|
|
|
endTime = endTime.Add(earlyRenewalPeriod)
|
|
|
|
|
|
|
|
if time.Now().After(endTime) {
|
|
|
|
// Treat an expired certificate as not existing, so we'll generate
|
|
|
|
// a new one with the next plan.
|
|
|
|
d.SetId("")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|