| package libtrust |
| |
| import ( |
| "crypto/tls" |
| "crypto/x509" |
| "fmt" |
| "io/ioutil" |
| "net" |
| "os" |
| "path" |
| "sync" |
| ) |
| |
| // ClientKeyManager manages client keys on the filesystem |
| type ClientKeyManager struct { |
| key PrivateKey |
| clientFile string |
| clientDir string |
| |
| clientLock sync.RWMutex |
| clients []PublicKey |
| |
| configLock sync.Mutex |
| configs []*tls.Config |
| } |
| |
| // NewClientKeyManager loads a new manager from a set of key files |
| // and managed by the given private key. |
| func NewClientKeyManager(trustKey PrivateKey, clientFile, clientDir string) (*ClientKeyManager, error) { |
| m := &ClientKeyManager{ |
| key: trustKey, |
| clientFile: clientFile, |
| clientDir: clientDir, |
| } |
| if err := m.loadKeys(); err != nil { |
| return nil, err |
| } |
| // TODO Start watching file and directory |
| |
| return m, nil |
| } |
| |
| func (c *ClientKeyManager) loadKeys() (err error) { |
| // Load authorized keys file |
| var clients []PublicKey |
| if c.clientFile != "" { |
| clients, err = LoadKeySetFile(c.clientFile) |
| if err != nil { |
| return fmt.Errorf("unable to load authorized keys: %s", err) |
| } |
| } |
| |
| // Add clients from authorized keys directory |
| files, err := ioutil.ReadDir(c.clientDir) |
| if err != nil && !os.IsNotExist(err) { |
| return fmt.Errorf("unable to open authorized keys directory: %s", err) |
| } |
| for _, f := range files { |
| if !f.IsDir() { |
| publicKey, err := LoadPublicKeyFile(path.Join(c.clientDir, f.Name())) |
| if err != nil { |
| return fmt.Errorf("unable to load authorized key file: %s", err) |
| } |
| clients = append(clients, publicKey) |
| } |
| } |
| |
| c.clientLock.Lock() |
| c.clients = clients |
| c.clientLock.Unlock() |
| |
| return nil |
| } |
| |
| // RegisterTLSConfig registers a tls configuration to manager |
| // such that any changes to the keys may be reflected in |
| // the tls client CA pool |
| func (c *ClientKeyManager) RegisterTLSConfig(tlsConfig *tls.Config) error { |
| c.clientLock.RLock() |
| certPool, err := GenerateCACertPool(c.key, c.clients) |
| if err != nil { |
| return fmt.Errorf("CA pool generation error: %s", err) |
| } |
| c.clientLock.RUnlock() |
| |
| tlsConfig.ClientCAs = certPool |
| |
| c.configLock.Lock() |
| c.configs = append(c.configs, tlsConfig) |
| c.configLock.Unlock() |
| |
| return nil |
| } |
| |
| // NewIdentityAuthTLSConfig creates a tls.Config for the server to use for |
| // libtrust identity authentication for the domain specified |
| func NewIdentityAuthTLSConfig(trustKey PrivateKey, clients *ClientKeyManager, addr string, domain string) (*tls.Config, error) { |
| tlsConfig := newTLSConfig() |
| |
| tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert |
| if err := clients.RegisterTLSConfig(tlsConfig); err != nil { |
| return nil, err |
| } |
| |
| // Generate cert |
| ips, domains, err := parseAddr(addr) |
| if err != nil { |
| return nil, err |
| } |
| // add domain that it expects clients to use |
| domains = append(domains, domain) |
| x509Cert, err := GenerateSelfSignedServerCert(trustKey, domains, ips) |
| if err != nil { |
| return nil, fmt.Errorf("certificate generation error: %s", err) |
| } |
| tlsConfig.Certificates = []tls.Certificate{{ |
| Certificate: [][]byte{x509Cert.Raw}, |
| PrivateKey: trustKey.CryptoPrivateKey(), |
| Leaf: x509Cert, |
| }} |
| |
| return tlsConfig, nil |
| } |
| |
| // NewCertAuthTLSConfig creates a tls.Config for the server to use for |
| // certificate authentication |
| func NewCertAuthTLSConfig(caPath, certPath, keyPath string) (*tls.Config, error) { |
| tlsConfig := newTLSConfig() |
| |
| cert, err := tls.LoadX509KeyPair(certPath, keyPath) |
| if err != nil { |
| return nil, fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?", certPath, keyPath, err) |
| } |
| tlsConfig.Certificates = []tls.Certificate{cert} |
| |
| // Verify client certificates against a CA? |
| if caPath != "" { |
| certPool := x509.NewCertPool() |
| file, err := ioutil.ReadFile(caPath) |
| if err != nil { |
| return nil, fmt.Errorf("Couldn't read CA certificate: %s", err) |
| } |
| certPool.AppendCertsFromPEM(file) |
| |
| tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert |
| tlsConfig.ClientCAs = certPool |
| } |
| |
| return tlsConfig, nil |
| } |
| |
| func newTLSConfig() *tls.Config { |
| return &tls.Config{ |
| NextProtos: []string{"http/1.1"}, |
| // Avoid fallback on insecure SSL protocols |
| MinVersion: tls.VersionTLS10, |
| } |
| } |
| |
| // parseAddr parses an address into an array of IPs and domains |
| func parseAddr(addr string) ([]net.IP, []string, error) { |
| host, _, err := net.SplitHostPort(addr) |
| if err != nil { |
| return nil, nil, err |
| } |
| var domains []string |
| var ips []net.IP |
| ip := net.ParseIP(host) |
| if ip != nil { |
| ips = []net.IP{ip} |
| } else { |
| domains = []string{host} |
| } |
| return ips, domains, nil |
| } |