2019-12-12 04:23:44 +00:00
|
|
|
package identity
|
2019-11-19 01:07:23 +00:00
|
|
|
|
|
|
|
import (
|
2019-11-20 19:50:46 +00:00
|
|
|
"bytes"
|
|
|
|
"crypto"
|
2019-11-19 01:07:23 +00:00
|
|
|
"crypto/tls"
|
2019-11-20 19:50:46 +00:00
|
|
|
"crypto/x509"
|
|
|
|
"encoding/json"
|
|
|
|
"encoding/pem"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2019-11-19 01:07:23 +00:00
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2019-11-20 19:50:46 +00:00
|
|
|
"time"
|
2019-11-19 01:07:23 +00:00
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2019-11-20 19:50:46 +00:00
|
|
|
"github.com/smallstep/certificates/api"
|
2019-11-19 01:07:23 +00:00
|
|
|
"github.com/smallstep/cli/config"
|
2019-11-20 19:50:46 +00:00
|
|
|
"github.com/smallstep/cli/crypto/pemutil"
|
2019-11-19 01:07:23 +00:00
|
|
|
)
|
|
|
|
|
2019-12-12 04:23:44 +00:00
|
|
|
// Type represents the different types of identity files.
|
|
|
|
type Type string
|
2019-11-27 02:48:28 +00:00
|
|
|
|
2019-11-20 19:50:46 +00:00
|
|
|
// Disabled represents a disabled identity type
|
2019-12-12 04:23:44 +00:00
|
|
|
const Disabled Type = ""
|
2019-11-20 19:50:46 +00:00
|
|
|
|
2019-11-19 01:07:23 +00:00
|
|
|
// MutualTLS represents the identity using mTLS
|
2019-12-12 04:23:44 +00:00
|
|
|
const MutualTLS Type = "mTLS"
|
2019-11-19 01:07:23 +00:00
|
|
|
|
2019-12-10 00:54:48 +00:00
|
|
|
// DefaultLeeway is the duration for matching not before claims.
|
|
|
|
const DefaultLeeway = 1 * time.Minute
|
|
|
|
|
2019-11-19 01:07:23 +00:00
|
|
|
// IdentityFile contains the location of the identity file.
|
|
|
|
var IdentityFile = filepath.Join(config.StepPath(), "config", "identity.json")
|
|
|
|
|
2019-12-12 04:23:44 +00:00
|
|
|
// DefaultsFile contains the location of the defaults file.
|
|
|
|
var DefaultsFile = filepath.Join(config.StepPath(), "config", "defaults.json")
|
|
|
|
|
2019-11-19 01:07:23 +00:00
|
|
|
// Identity represents the identity file that can be used to authenticate with
|
|
|
|
// the CA.
|
|
|
|
type Identity struct {
|
|
|
|
Type string `json:"type"`
|
|
|
|
Certificate string `json:"crt"`
|
|
|
|
Key string `json:"key"`
|
|
|
|
}
|
|
|
|
|
2019-11-21 00:03:31 +00:00
|
|
|
// LoadDefaultIdentity loads the default identity.
|
|
|
|
func LoadDefaultIdentity() (*Identity, error) {
|
|
|
|
b, err := ioutil.ReadFile(IdentityFile)
|
|
|
|
if err != nil {
|
2019-12-12 04:23:44 +00:00
|
|
|
return nil, errors.Wrapf(err, "error reading %s", IdentityFile)
|
2019-11-21 00:03:31 +00:00
|
|
|
}
|
|
|
|
identity := new(Identity)
|
|
|
|
if err := json.Unmarshal(b, &identity); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "error unmarshaling %s", IdentityFile)
|
|
|
|
}
|
|
|
|
return identity, nil
|
|
|
|
}
|
|
|
|
|
2019-12-12 20:23:53 +00:00
|
|
|
// configDir and identityDir are used in WriteDefaultIdentity for testing
|
|
|
|
// purposes.
|
|
|
|
var (
|
|
|
|
configDir = filepath.Join(config.StepPath(), "config")
|
|
|
|
identityDir = filepath.Join(config.StepPath(), "identity")
|
|
|
|
)
|
|
|
|
|
2019-11-20 19:50:46 +00:00
|
|
|
// WriteDefaultIdentity writes the given certificates and key and the
|
|
|
|
// identity.json pointing to the new files.
|
|
|
|
func WriteDefaultIdentity(certChain []api.Certificate, key crypto.PrivateKey) error {
|
2019-12-12 20:23:53 +00:00
|
|
|
if err := os.MkdirAll(configDir, 0700); err != nil {
|
2019-11-20 19:50:46 +00:00
|
|
|
return errors.Wrap(err, "error creating config directory")
|
|
|
|
}
|
|
|
|
|
2019-12-12 20:23:53 +00:00
|
|
|
if err := os.MkdirAll(identityDir, 0700); err != nil {
|
2019-11-20 19:50:46 +00:00
|
|
|
return errors.Wrap(err, "error creating identity directory")
|
|
|
|
}
|
|
|
|
|
2019-12-12 20:23:53 +00:00
|
|
|
certFilename := filepath.Join(identityDir, "identity.crt")
|
|
|
|
keyFilename := filepath.Join(identityDir, "identity_key")
|
2019-11-20 19:50:46 +00:00
|
|
|
|
|
|
|
// Write certificate
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
for _, crt := range certChain {
|
|
|
|
block := &pem.Block{
|
|
|
|
Type: "CERTIFICATE",
|
|
|
|
Bytes: crt.Raw,
|
|
|
|
}
|
|
|
|
if err := pem.Encode(buf, block); err != nil {
|
|
|
|
return errors.Wrap(err, "error encoding identity certificate")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := ioutil.WriteFile(certFilename, buf.Bytes(), 0600); err != nil {
|
|
|
|
return errors.Wrap(err, "error writing identity certificate")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write key
|
|
|
|
buf.Reset()
|
|
|
|
block, err := pemutil.Serialize(key)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := pem.Encode(buf, block); err != nil {
|
|
|
|
return errors.Wrap(err, "error encoding identity key")
|
|
|
|
}
|
|
|
|
if err := ioutil.WriteFile(keyFilename, buf.Bytes(), 0600); err != nil {
|
|
|
|
return errors.Wrap(err, "error writing identity certificate")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write identity.json
|
|
|
|
buf.Reset()
|
|
|
|
enc := json.NewEncoder(buf)
|
|
|
|
enc.SetIndent("", " ")
|
|
|
|
if err := enc.Encode(Identity{
|
|
|
|
Type: string(MutualTLS),
|
|
|
|
Certificate: certFilename,
|
|
|
|
Key: keyFilename,
|
|
|
|
}); err != nil {
|
|
|
|
return errors.Wrap(err, "error writing identity json")
|
|
|
|
}
|
|
|
|
if err := ioutil.WriteFile(IdentityFile, buf.Bytes(), 0600); err != nil {
|
|
|
|
return errors.Wrap(err, "error writing identity certificate")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-19 01:07:23 +00:00
|
|
|
// Kind returns the type for the given identity.
|
2019-12-12 04:23:44 +00:00
|
|
|
func (i *Identity) Kind() Type {
|
2019-11-19 01:07:23 +00:00
|
|
|
switch strings.ToLower(i.Type) {
|
2019-11-20 19:50:46 +00:00
|
|
|
case "":
|
|
|
|
return Disabled
|
2019-11-19 01:07:23 +00:00
|
|
|
case "mtls":
|
|
|
|
return MutualTLS
|
|
|
|
default:
|
2019-12-12 04:23:44 +00:00
|
|
|
return Type(i.Type)
|
2019-11-19 01:07:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate validates the identity object.
|
|
|
|
func (i *Identity) Validate() error {
|
|
|
|
switch i.Kind() {
|
2019-11-20 19:50:46 +00:00
|
|
|
case Disabled:
|
|
|
|
return nil
|
2019-11-19 01:07:23 +00:00
|
|
|
case MutualTLS:
|
|
|
|
if i.Certificate == "" {
|
|
|
|
return errors.New("identity.crt cannot be empty")
|
|
|
|
}
|
|
|
|
if i.Key == "" {
|
|
|
|
return errors.New("identity.key cannot be empty")
|
|
|
|
}
|
2019-12-12 04:23:44 +00:00
|
|
|
if err := fileExists(i.Certificate); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := fileExists(i.Key); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-11-19 01:07:23 +00:00
|
|
|
return nil
|
|
|
|
default:
|
|
|
|
return errors.Errorf("unsupported identity type %s", i.Type)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-12 04:23:44 +00:00
|
|
|
// TLSCertificate returns a tls.Certificate for the identity.
|
|
|
|
func (i *Identity) TLSCertificate() (tls.Certificate, error) {
|
|
|
|
fail := func(err error) (tls.Certificate, error) { return tls.Certificate{}, err }
|
2019-11-19 01:07:23 +00:00
|
|
|
switch i.Kind() {
|
2019-11-20 19:50:46 +00:00
|
|
|
case Disabled:
|
2019-12-12 04:23:44 +00:00
|
|
|
return tls.Certificate{}, nil
|
2019-11-19 01:07:23 +00:00
|
|
|
case MutualTLS:
|
|
|
|
crt, err := tls.LoadX509KeyPair(i.Certificate, i.Key)
|
|
|
|
if err != nil {
|
2019-12-12 04:23:44 +00:00
|
|
|
return fail(errors.Wrap(err, "error creating identity certificate"))
|
2019-11-19 01:07:23 +00:00
|
|
|
}
|
2019-12-12 04:23:44 +00:00
|
|
|
|
2019-11-20 19:50:46 +00:00
|
|
|
// Check if certificate is expired.
|
|
|
|
x509Cert, err := x509.ParseCertificate(crt.Certificate[0])
|
|
|
|
if err != nil {
|
2019-12-12 04:23:44 +00:00
|
|
|
return fail(errors.Wrap(err, "error creating identity certificate"))
|
2019-11-20 19:50:46 +00:00
|
|
|
}
|
2019-12-10 00:54:48 +00:00
|
|
|
now := time.Now().Truncate(time.Second)
|
2019-12-12 04:23:44 +00:00
|
|
|
if now.Add(DefaultLeeway).Before(x509Cert.NotBefore) {
|
|
|
|
return fail(errors.New("certificate is not yet valid"))
|
2019-11-20 19:50:46 +00:00
|
|
|
}
|
2019-12-12 04:23:44 +00:00
|
|
|
if now.After(x509Cert.NotAfter) {
|
|
|
|
return fail(errors.New("certificate is already expired"))
|
|
|
|
}
|
|
|
|
return crt, nil
|
2019-11-19 01:07:23 +00:00
|
|
|
default:
|
2019-12-12 04:23:44 +00:00
|
|
|
return fail(errors.Errorf("unsupported identity type %s", i.Type))
|
2019-11-19 01:07:23 +00:00
|
|
|
}
|
|
|
|
}
|
2019-11-21 00:03:31 +00:00
|
|
|
|
2019-12-12 04:23:44 +00:00
|
|
|
func fileExists(filename string) error {
|
|
|
|
info, err := os.Stat(filename)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "error reading %s", filename)
|
|
|
|
}
|
|
|
|
if info.IsDir() {
|
|
|
|
return errors.Errorf("error reading %s: file is a directory", filename)
|
2019-11-21 00:03:31 +00:00
|
|
|
}
|
2019-12-12 04:23:44 +00:00
|
|
|
return nil
|
2019-11-21 00:03:31 +00:00
|
|
|
}
|