package crypto import ( "bytes" "crypto" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "errors" "fmt" "io/ioutil" "math/big" mathrand "math/rand" "net" "os" "path/filepath" "strconv" "sync" "time" "github.com/golang/glog" "k8s.io/kubernetes/pkg/auth/user" "k8s.io/kubernetes/pkg/util/sets" "github.com/openshift/origin/pkg/auth/authenticator/request/x509request" cmdutil "github.com/openshift/origin/pkg/cmd/util" ) // SecureTLSConfig enforces the default minimum security settings for the // cluster. // TODO: allow override func SecureTLSConfig(config *tls.Config) *tls.Config { // Recommendations from https://wiki.mozilla.org/Security/Server_Side_TLS // Can't use SSLv3 because of POODLE and BEAST // Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher // Can't use TLSv1.1 because of RC4 cipher usage config.MinVersion = tls.VersionTLS12 // In a legacy environment, allow cipher control to be disabled. if len(os.Getenv("OPENSHIFT_ALLOW_DANGEROUS_TLS_CIPHER_SUITES")) == 0 { config.PreferServerCipherSuites = true config.CipherSuites = []uint16{ // Ciphers below are selected and ordered based on the recommended "Intermediate compatibility" suite // Compare with available ciphers when bumping Go versions // // Available ciphers from last comparison (go 1.6): // TLS_RSA_WITH_RC4_128_SHA - no // TLS_RSA_WITH_3DES_EDE_CBC_SHA // TLS_RSA_WITH_AES_128_CBC_SHA // TLS_RSA_WITH_AES_256_CBC_SHA // TLS_RSA_WITH_AES_128_GCM_SHA256 // TLS_RSA_WITH_AES_256_GCM_SHA384 // TLS_ECDHE_ECDSA_WITH_RC4_128_SHA - no // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA // TLS_ECDHE_RSA_WITH_RC4_128_SHA - no // TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, // the next two are in the intermediate suite, but go1.6 http2 complains when they are included at the recommended index // fixed in https://github.com/golang/go/commit/b5aae1a2845f157a2565b856fb2d7773a0f7af25 in go1.7 // tls.TLS_RSA_WITH_AES_128_GCM_SHA256, // tls.TLS_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, } } else { glog.Warningf("Potentially insecure TLS cipher suites are allowed in client connections because environment variable OPENSHIFT_ALLOW_DANGEROUS_TLS_CIPHER_SUITES is set") } return config } type TLSCertificateConfig struct { Certs []*x509.Certificate Key crypto.PrivateKey } type TLSCARoots struct { Roots []*x509.Certificate } func (c *TLSCertificateConfig) writeCertConfig(certFile, keyFile string) error { if err := writeCertificates(certFile, c.Certs...); err != nil { return err } if err := writeKeyFile(keyFile, c.Key); err != nil { return err } return nil } func (c *TLSCertificateConfig) GetPEMBytes() ([]byte, []byte, error) { certBytes, err := encodeCertificates(c.Certs...) if err != nil { return nil, nil, err } keyBytes, err := encodeKey(c.Key) if err != nil { return nil, nil, err } return certBytes, keyBytes, nil } func (c *TLSCARoots) writeCARoots(rootFile string) error { if err := writeCertificates(rootFile, c.Roots...); err != nil { return err } return nil } func GetTLSCARoots(caFile string) (*TLSCARoots, error) { if len(caFile) == 0 { return nil, errors.New("caFile missing") } caPEMBlock, err := ioutil.ReadFile(caFile) if err != nil { return nil, err } roots, err := cmdutil.CertificatesFromPEM(caPEMBlock) if err != nil { return nil, fmt.Errorf("Error reading %s: %s", caFile, err) } return &TLSCARoots{roots}, nil } func GetTLSCertificateConfig(certFile, keyFile string) (*TLSCertificateConfig, error) { if len(certFile) == 0 { return nil, errors.New("certFile missing") } if len(keyFile) == 0 { return nil, errors.New("keyFile missing") } certPEMBlock, err := ioutil.ReadFile(certFile) if err != nil { return nil, err } certs, err := cmdutil.CertificatesFromPEM(certPEMBlock) if err != nil { return nil, fmt.Errorf("Error reading %s: %s", certFile, err) } keyPEMBlock, err := ioutil.ReadFile(keyFile) if err != nil { return nil, err } keyPairCert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock) if err != nil { return nil, err } key := keyPairCert.PrivateKey return &TLSCertificateConfig{certs, key}, nil } const ( DefaultCertificateLifetimeInDays = 365 * 2 // 2 years DefaultCACertificateLifetimeInDays = 365 * 5 // 5 years // Default keys are 2048 bits keyBits = 2048 ) type CA struct { Config *TLSCertificateConfig SerialGenerator SerialGenerator } // SerialGenerator is an interface for getting a serial number for the cert. It MUST be thread-safe. type SerialGenerator interface { Next(template *x509.Certificate) (int64, error) } // SerialFileGenerator returns a unique, monotonically increasing serial number and ensures the CA on disk records that value. type SerialFileGenerator struct { SerialFile string // lock guards access to the Serial field lock sync.Mutex Serial int64 } func NewSerialFileGenerator(serialFile string, createIfNeeded bool) (*SerialFileGenerator, error) { // read serial file var serial int64 serialData, err := ioutil.ReadFile(serialFile) if err == nil { serial, _ = strconv.ParseInt(string(serialData), 16, 64) } if os.IsNotExist(err) && createIfNeeded { if err := ioutil.WriteFile(serialFile, []byte("00"), 0644); err != nil { return nil, err } serial = 1 } else if err != nil { return nil, err } if serial < 1 { serial = 1 } return &SerialFileGenerator{ Serial: serial, SerialFile: serialFile, }, nil } // Next returns a unique, monotonically increasing serial number and ensures the CA on disk records that value. func (s *SerialFileGenerator) Next(template *x509.Certificate) (int64, error) { s.lock.Lock() defer s.lock.Unlock() next := s.Serial + 1 s.Serial = next // Output in hex, padded to multiples of two characters for OpenSSL's sake serialText := fmt.Sprintf("%X", next) if len(serialText)%2 == 1 { serialText = "0" + serialText } if err := ioutil.WriteFile(s.SerialFile, []byte(serialText), os.FileMode(0640)); err != nil { return 0, err } return next, nil } // RandomSerialGenerator returns a serial based on time.Now and the subject type RandomSerialGenerator struct { } func (s *RandomSerialGenerator) Next(template *x509.Certificate) (int64, error) { r := mathrand.New(mathrand.NewSource(time.Now().UTC().UnixNano())) return r.Int63(), nil } // EnsureCA returns a CA, whether it was created (as opposed to pre-existing), and any error // if serialFile is empty, a RandomSerialGenerator will be used func EnsureCA(certFile, keyFile, serialFile, name string, expireDays int) (*CA, bool, error) { if ca, err := GetCA(certFile, keyFile, serialFile); err == nil { return ca, false, err } ca, err := MakeCA(certFile, keyFile, serialFile, name, expireDays) return ca, true, err } // if serialFile is empty, a RandomSerialGenerator will be used func GetCA(certFile, keyFile, serialFile string) (*CA, error) { caConfig, err := GetTLSCertificateConfig(certFile, keyFile) if err != nil { return nil, err } var serialGenerator SerialGenerator if len(serialFile) > 0 { serialGenerator, err = NewSerialFileGenerator(serialFile, false) if err != nil { return nil, err } } else { serialGenerator = &RandomSerialGenerator{} } return &CA{ SerialGenerator: serialGenerator, Config: caConfig, }, nil } // if serialFile is empty, a RandomSerialGenerator will be used func MakeCA(certFile, keyFile, serialFile, name string, expireDays int) (*CA, error) { glog.V(2).Infof("Generating new CA for %s cert, and key in %s, %s", name, certFile, keyFile) // Create CA cert rootcaPublicKey, rootcaPrivateKey, err := NewKeyPair() if err != nil { return nil, err } rootcaTemplate := newSigningCertificateTemplate(pkix.Name{CommonName: name}, expireDays, time.Now) rootcaCert, err := signCertificate(rootcaTemplate, rootcaPublicKey, rootcaTemplate, rootcaPrivateKey) if err != nil { return nil, err } caConfig := &TLSCertificateConfig{ Certs: []*x509.Certificate{rootcaCert}, Key: rootcaPrivateKey, } if err := caConfig.writeCertConfig(certFile, keyFile); err != nil { return nil, err } var serialGenerator SerialGenerator if len(serialFile) > 0 { if err := ioutil.WriteFile(serialFile, []byte("00"), 0644); err != nil { return nil, err } serialGenerator, err = NewSerialFileGenerator(serialFile, false) if err != nil { return nil, err } } else { serialGenerator = &RandomSerialGenerator{} } return &CA{ SerialGenerator: serialGenerator, Config: caConfig, }, nil } func (ca *CA) EnsureServerCert(certFile, keyFile string, hostnames sets.String, expireDays int) (*TLSCertificateConfig, bool, error) { certConfig, err := GetServerCert(certFile, keyFile, hostnames) if err != nil { certConfig, err = ca.MakeAndWriteServerCert(certFile, keyFile, hostnames, expireDays) return certConfig, true, err } return certConfig, false, nil } func GetServerCert(certFile, keyFile string, hostnames sets.String) (*TLSCertificateConfig, error) { server, err := GetTLSCertificateConfig(certFile, keyFile) if err != nil { return nil, err } cert := server.Certs[0] ips, dns := IPAddressesDNSNames(hostnames.List()) missingIps := ipsNotInSlice(ips, cert.IPAddresses) missingDns := stringsNotInSlice(dns, cert.DNSNames) if len(missingIps) == 0 && len(missingDns) == 0 { glog.V(4).Infof("Found existing server certificate in %s", certFile) return server, nil } return nil, fmt.Errorf("Existing server certificate in %s was missing some hostnames (%v) or IP addresses (%v).", certFile, missingDns, missingIps) } func (ca *CA) MakeAndWriteServerCert(certFile, keyFile string, hostnames sets.String, expireDays int) (*TLSCertificateConfig, error) { glog.V(4).Infof("Generating server certificate in %s, key in %s", certFile, keyFile) server, err := ca.MakeServerCert(hostnames, expireDays) if err != nil { return nil, err } if err := server.writeCertConfig(certFile, keyFile); err != nil { return server, err } return server, nil } func (ca *CA) MakeServerCert(hostnames sets.String, expireDays int) (*TLSCertificateConfig, error) { serverPublicKey, serverPrivateKey, _ := NewKeyPair() serverTemplate := newServerCertificateTemplate(pkix.Name{CommonName: hostnames.List()[0]}, hostnames.List(), expireDays, time.Now) serverCrt, err := ca.signCertificate(serverTemplate, serverPublicKey) if err != nil { return nil, err } server := &TLSCertificateConfig{ Certs: append([]*x509.Certificate{serverCrt}, ca.Config.Certs...), Key: serverPrivateKey, } return server, nil } func (ca *CA) EnsureClientCertificate(certFile, keyFile string, u user.Info, expireDays int) (*TLSCertificateConfig, bool, error) { certConfig, err := GetTLSCertificateConfig(certFile, keyFile) if err != nil { certConfig, err = ca.MakeClientCertificate(certFile, keyFile, u, expireDays) return certConfig, true, err // true indicates we wrote the files. } return certConfig, false, nil } func (ca *CA) MakeClientCertificate(certFile, keyFile string, u user.Info, expireDays int) (*TLSCertificateConfig, error) { glog.V(4).Infof("Generating client cert in %s and key in %s", certFile, keyFile) // ensure parent dirs if err := os.MkdirAll(filepath.Dir(certFile), os.FileMode(0755)); err != nil { return nil, err } if err := os.MkdirAll(filepath.Dir(keyFile), os.FileMode(0755)); err != nil { return nil, err } clientPublicKey, clientPrivateKey, _ := NewKeyPair() clientTemplate := newClientCertificateTemplate(x509request.UserToSubject(u), expireDays, time.Now) clientCrt, err := ca.signCertificate(clientTemplate, clientPublicKey) if err != nil { return nil, err } certData, err := encodeCertificates(clientCrt) if err != nil { return nil, err } keyData, err := encodeKey(clientPrivateKey) if err != nil { return nil, err } if err = ioutil.WriteFile(certFile, certData, os.FileMode(0644)); err != nil { return nil, err } if err = ioutil.WriteFile(keyFile, keyData, os.FileMode(0600)); err != nil { return nil, err } return GetTLSCertificateConfig(certFile, keyFile) } func (ca *CA) signCertificate(template *x509.Certificate, requestKey crypto.PublicKey) (*x509.Certificate, error) { // Increment and persist serial serial, err := ca.SerialGenerator.Next(template) if err != nil { return nil, err } template.SerialNumber = big.NewInt(serial) return signCertificate(template, requestKey, ca.Config.Certs[0], ca.Config.Key) } func NewKeyPair() (crypto.PublicKey, crypto.PrivateKey, error) { privateKey, err := rsa.GenerateKey(rand.Reader, keyBits) if err != nil { return nil, nil, err } return &privateKey.PublicKey, privateKey, nil } // Can be used for CA or intermediate signing certs func newSigningCertificateTemplate(subject pkix.Name, expireDays int, currentTime func() time.Time) *x509.Certificate { var caLifetimeInDays = DefaultCACertificateLifetimeInDays if expireDays > 0 { caLifetimeInDays = expireDays } if caLifetimeInDays > DefaultCACertificateLifetimeInDays { warnAboutCertificateLifeTime(subject.CommonName, DefaultCACertificateLifetimeInDays) } caLifetime := time.Duration(caLifetimeInDays) * 24 * time.Hour return &x509.Certificate{ Subject: subject, SignatureAlgorithm: x509.SHA256WithRSA, NotBefore: currentTime().Add(-1 * time.Second), NotAfter: currentTime().Add(caLifetime), SerialNumber: big.NewInt(1), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, IsCA: true, } } // Can be used for ListenAndServeTLS func newServerCertificateTemplate(subject pkix.Name, hosts []string, expireDays int, currentTime func() time.Time) *x509.Certificate { var lifetimeInDays = DefaultCertificateLifetimeInDays if expireDays > 0 { lifetimeInDays = expireDays } if lifetimeInDays > DefaultCertificateLifetimeInDays { warnAboutCertificateLifeTime(subject.CommonName, DefaultCertificateLifetimeInDays) } lifetime := time.Duration(lifetimeInDays) * 24 * time.Hour template := &x509.Certificate{ Subject: subject, SignatureAlgorithm: x509.SHA256WithRSA, NotBefore: currentTime().Add(-1 * time.Second), NotAfter: currentTime().Add(lifetime), SerialNumber: big.NewInt(1), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, } template.IPAddresses, template.DNSNames = IPAddressesDNSNames(hosts) return template } func IPAddressesDNSNames(hosts []string) ([]net.IP, []string) { ips := []net.IP{} dns := []string{} for _, host := range hosts { if ip := net.ParseIP(host); ip != nil { ips = append(ips, ip) } else { dns = append(dns, host) } } // Include IP addresses as DNS subjectAltNames in the cert as well, for the sake of Python, Windows (< 10), and unnamed other libraries // Ensure these technically invalid DNS subjectAltNames occur after the valid ones, to avoid triggering cert errors in Firefox // See https://bugzilla.mozilla.org/show_bug.cgi?id=1148766 for _, ip := range ips { dns = append(dns, ip.String()) } return ips, dns } func CertsFromPEM(pemCerts []byte) ([]*x509.Certificate, error) { ok := false certs := []*x509.Certificate{} for len(pemCerts) > 0 { var block *pem.Block block, pemCerts = pem.Decode(pemCerts) if block == nil { break } if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { continue } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return certs, err } certs = append(certs, cert) ok = true } if !ok { return certs, errors.New("Could not read any certificates") } return certs, nil } // Can be used as a certificate in http.Transport TLSClientConfig func newClientCertificateTemplate(subject pkix.Name, expireDays int, currentTime func() time.Time) *x509.Certificate { var lifetimeInDays = DefaultCertificateLifetimeInDays if expireDays > 0 { lifetimeInDays = expireDays } if lifetimeInDays > DefaultCertificateLifetimeInDays { warnAboutCertificateLifeTime(subject.CommonName, DefaultCertificateLifetimeInDays) } lifetime := time.Duration(lifetimeInDays) * 24 * time.Hour return &x509.Certificate{ Subject: subject, SignatureAlgorithm: x509.SHA256WithRSA, NotBefore: currentTime().Add(-1 * time.Second), NotAfter: currentTime().Add(lifetime), SerialNumber: big.NewInt(1), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, BasicConstraintsValid: true, } } func warnAboutCertificateLifeTime(name string, defaultLifetimeInDays int) { defaultLifetimeInYears := defaultLifetimeInDays / 365 fmt.Fprintf(os.Stderr, "WARNING: Validity period of the certificate for %q is greater than %d years!\n", name, defaultLifetimeInYears) fmt.Fprintln(os.Stderr, "WARNING: By security reasons it is strongly recommended to change this period and make it smaller!") } func signCertificate(template *x509.Certificate, requestKey crypto.PublicKey, issuer *x509.Certificate, issuerKey crypto.PrivateKey) (*x509.Certificate, error) { derBytes, err := x509.CreateCertificate(rand.Reader, template, issuer, requestKey, issuerKey) if err != nil { return nil, err } certs, err := x509.ParseCertificates(derBytes) if err != nil { return nil, err } if len(certs) != 1 { return nil, errors.New("Expected a single certificate") } return certs[0], nil } func encodeCertificates(certs ...*x509.Certificate) ([]byte, error) { b := bytes.Buffer{} for _, cert := range certs { if err := pem.Encode(&b, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil { return []byte{}, err } } return b.Bytes(), nil } func encodeKey(key crypto.PrivateKey) ([]byte, error) { b := bytes.Buffer{} switch key := key.(type) { case *ecdsa.PrivateKey: keyBytes, err := x509.MarshalECPrivateKey(key) if err != nil { return []byte{}, err } if err := pem.Encode(&b, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}); err != nil { return b.Bytes(), err } case *rsa.PrivateKey: if err := pem.Encode(&b, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}); err != nil { return []byte{}, err } default: return []byte{}, errors.New("Unrecognized key type") } return b.Bytes(), nil } func writeCertificates(path string, certs ...*x509.Certificate) error { // ensure parent dir if err := os.MkdirAll(filepath.Dir(path), os.FileMode(0755)); err != nil { return err } bytes, err := encodeCertificates(certs...) if err != nil { return err } return ioutil.WriteFile(path, bytes, os.FileMode(0644)) } func writeKeyFile(path string, key crypto.PrivateKey) error { // ensure parent dir if err := os.MkdirAll(filepath.Dir(path), os.FileMode(0755)); err != nil { return err } b, err := encodeKey(key) if err != nil { return err } return ioutil.WriteFile(path, b, os.FileMode(0600)) } func stringsNotInSlice(needles []string, haystack []string) []string { missing := []string{} for _, needle := range needles { if !stringInSlice(needle, haystack) { missing = append(missing, needle) } } return missing } func stringInSlice(needle string, haystack []string) bool { for _, straw := range haystack { if needle == straw { return true } } return false } func ipsNotInSlice(needles []net.IP, haystack []net.IP) []net.IP { missing := []net.IP{} for _, needle := range needles { if !ipInSlice(needle, haystack) { missing = append(missing, needle) } } return missing } func ipInSlice(needle net.IP, haystack []net.IP) bool { for _, straw := range haystack { if needle.Equal(straw) { return true } } return false }