123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- package ca
- import (
- "crypto/x509"
- "encoding/json"
- "encoding/pem"
- "errors"
- "fmt"
- "net"
- "path/filepath"
- "github.com/cloudflare/cfssl/api/client"
- "github.com/cloudflare/cfssl/auth"
- "github.com/cloudflare/cfssl/config"
- "github.com/cloudflare/cfssl/helpers"
- "github.com/cloudflare/cfssl/info"
- "github.com/cloudflare/cfssl/signer"
- "github.com/cloudflare/cfssl/transport/core"
- )
- type authError struct {
- authType string
- }
- func (err *authError) Error() string {
- return fmt.Sprintf("transport: unsupported CFSSL authentication method %s", err.authType)
- }
- // This approach allows us to quickly add other providers later, such
- // as the TPM.
- var authTypes = map[string]func(config.AuthKey, []byte) (auth.Provider, error){
- "standard": newStandardProvider,
- }
- // Create a standard provider without providing any additional data.
- func newStandardProvider(ak config.AuthKey, ad []byte) (auth.Provider, error) {
- return auth.New(ak.Key, ad)
- }
- // Create a new provider from an authentication key and possibly
- // additional data.
- func newProvider(ak config.AuthKey, ad []byte) (auth.Provider, error) {
- // If no auth key was provided, use unauthenticated
- // requests. This is useful when a local CFSSL is being used.
- if ak.Type == "" && ak.Key == "" {
- return nil, nil
- }
- f, ok := authTypes[ak.Type]
- if !ok {
- return nil, &authError{authType: ak.Type}
- }
- return f(ak, ad)
- }
- // ErrNoAuth is returned when a client is talking to a CFSSL remote
- // that is not on a loopback address and doesn't have an
- // authentication provider set.
- var ErrNoAuth = errors.New("transport: authentication is required for non-local remotes")
- var v4Loopback = net.IPNet{
- IP: net.IP{127, 0, 0, 0},
- Mask: net.IPv4Mask(255, 0, 0, 0),
- }
- func ipIsLocal(ip net.IP) bool {
- if ip.To4() == nil {
- return ip.Equal(net.IPv6loopback)
- }
- return v4Loopback.Contains(ip)
- }
- // The only time a client should be doing unauthenticated requests is
- // when a local CFSSL is being used.
- func (cap *CFSSL) validateAuth() error {
- // The client is using some form of authentication, and the best way
- // to figure out that the auth is invalid is when it's used. Therefore,
- // we'll delay checking the credentials until that time.
- if cap.provider != nil {
- return nil
- }
- hosts := cap.remote.Hosts()
- for i := range hosts {
- ips, err := net.LookupIP(hosts[i])
- if err != nil {
- return err
- }
- for _, ip := range ips {
- if !ipIsLocal(ip) {
- return ErrNoAuth
- }
- }
- }
- return nil
- }
- var cfsslConfigDirs = []string{
- "/usr/local/cfssl",
- "/etc/cfssl",
- "/state/etc/cfssl",
- }
- // The CFSSL standard is to have a configuration file for a label as
- // <config>.json.
- func findLabel(label string) *config.Config {
- for _, dir := range cfsslConfigDirs {
- cfgFile := filepath.Join(dir, label+".json")
- cfg, err := config.LoadFile(cfgFile)
- if err == nil {
- return cfg
- }
- }
- return nil
- }
- func getProfile(cfg *config.Config, profileName string) (*config.SigningProfile, bool) {
- if cfg == nil || cfg.Signing == nil || cfg.Signing.Default == nil {
- return nil, false
- }
- var ok bool
- profile := cfg.Signing.Default
- if profileName != "" {
- if cfg.Signing.Profiles == nil {
- return nil, false
- }
- profile, ok = cfg.Signing.Profiles[profileName]
- if !ok {
- return nil, false
- }
- }
- return profile, true
- }
- // loadAuth loads an authentication provider from the client config's
- // explicitly set auth key.
- func (cap *CFSSL) loadAuth() error {
- var err error
- cap.provider, err = newProvider(cap.DefaultAuth, nil)
- return err
- }
- func getRemote(cfg *config.Config, profile *config.SigningProfile) (string, bool) {
- // NB: Loading the config will validate that the remote is
- // present in the config's remote section.
- if profile.RemoteServer != "" {
- return profile.RemoteServer, true
- }
- return "", false
- }
- func (cap *CFSSL) setRemoteAndAuth() error {
- if cap.Label != "" {
- cfsslConfig := findLabel(cap.Label)
- profile, ok := getProfile(cfsslConfig, cap.Profile)
- if ok {
- remote, ok := getRemote(cfsslConfig, profile)
- if ok {
- cap.remote = client.NewServerTLS(remote, helpers.CreateTLSConfig(profile.RemoteCAs, profile.ClientCert))
- cap.provider = profile.Provider
- return nil
- }
- // The profile may not have a remote set, but
- // it may have an authentication provider.
- cap.provider = profile.Provider
- }
- }
- cap.remote = cap.DefaultRemote
- if cap.provider != nil {
- return nil
- }
- return cap.loadAuth()
- }
- // CFSSL provides support for signing certificates via
- // CFSSL.
- type CFSSL struct {
- remote client.Remote
- provider auth.Provider
- Profile string
- Label string
- DefaultRemote client.Remote
- DefaultAuth config.AuthKey
- }
- // SignCSR requests a certificate from a CFSSL signer.
- func (cap *CFSSL) SignCSR(csrPEM []byte) (cert []byte, err error) {
- p, _ := pem.Decode(csrPEM)
- if p == nil || p.Type != "CERTIFICATE REQUEST" {
- return nil, errors.New("transport: invalid PEM-encoded certificate signing request")
- }
- csr, err := x509.ParseCertificateRequest(p.Bytes)
- if err != nil {
- return nil, err
- }
- hosts := make([]string, len(csr.DNSNames), len(csr.DNSNames)+len(csr.IPAddresses)+len(csr.URIs))
- copy(hosts, csr.DNSNames)
- for i := range csr.IPAddresses {
- hosts = append(hosts, csr.IPAddresses[i].String())
- }
- for i := range csr.URIs {
- hosts = append(hosts, csr.URIs[i].String())
- }
- sreq := &signer.SignRequest{
- Hosts: hosts,
- Request: string(csrPEM),
- Profile: cap.Profile,
- Label: cap.Label,
- }
- out, err := json.Marshal(sreq)
- if err != nil {
- return nil, err
- }
- if cap.provider != nil {
- return cap.remote.AuthSign(out, nil, cap.provider)
- }
- return cap.remote.Sign(out)
- }
- // CACertificate returns the certificate for a CFSSL CA.
- func (cap *CFSSL) CACertificate() ([]byte, error) {
- req := &info.Req{
- Label: cap.Label,
- Profile: cap.Profile,
- }
- out, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- resp, err := cap.remote.Info(out)
- if err != nil {
- return nil, err
- }
- return []byte(resp.Certificate), nil
- }
- // NewCFSSLProvider takes the configuration information from an
- // Identity (and an optional default remote), returning a CFSSL
- // instance. There should be a profile in id called "cfssl", which
- // should contain label and profile fields as needed.
- func NewCFSSLProvider(id *core.Identity, defaultRemote client.Remote) (*CFSSL, error) {
- if id == nil {
- return nil, errors.New("transport: the identity hasn't been initialised. Has it been loaded from disk?")
- }
- cap := &CFSSL{
- DefaultRemote: defaultRemote,
- }
- cfssl := id.Profiles["cfssl"]
- if cfssl != nil {
- cap.Label = cfssl["label"]
- cap.Profile = cfssl["profile"]
- if cap.DefaultRemote == nil {
- cert, err := helpers.LoadClientCertificate(cfssl["mutual-tls-cert"], cfssl["mutual-tls-key"])
- if err != nil {
- return nil, err
- }
- remoteCAs, err := helpers.LoadPEMCertPool(cfssl["tls-remote-ca"])
- if err != nil {
- return nil, err
- }
- cap.DefaultRemote = client.NewServerTLS(cfssl["remote"], helpers.CreateTLSConfig(remoteCAs, cert))
- }
- cap.DefaultAuth.Type = cfssl["auth-type"]
- cap.DefaultAuth.Key = cfssl["auth-key"]
- }
- err := cap.setRemoteAndAuth()
- if err != nil {
- return nil, err
- }
- return cap, nil
- }
|