660 lines
20 KiB
Go
660 lines
20 KiB
Go
// Package config contains the configuration logic for CFSSL.
|
|
package config
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/asn1"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cloudflare/cfssl/auth"
|
|
cferr "github.com/cloudflare/cfssl/errors"
|
|
"github.com/cloudflare/cfssl/helpers"
|
|
"github.com/cloudflare/cfssl/log"
|
|
ocspConfig "github.com/cloudflare/cfssl/ocsp/config"
|
|
)
|
|
|
|
// A CSRWhitelist stores booleans for fields in the CSR. If a CSRWhitelist is
|
|
// not present in a SigningProfile, all of these fields may be copied from the
|
|
// CSR into the signed certificate. If a CSRWhitelist *is* present in a
|
|
// SigningProfile, only those fields with a `true` value in the CSRWhitelist may
|
|
// be copied from the CSR to the signed certificate. Note that some of these
|
|
// fields, like Subject, can be provided or partially provided through the API.
|
|
// Since API clients are expected to be trusted, but CSRs are not, fields
|
|
// provided through the API are not subject to whitelisting through this
|
|
// mechanism.
|
|
type CSRWhitelist struct {
|
|
Subject, PublicKeyAlgorithm, PublicKey, SignatureAlgorithm bool
|
|
DNSNames, IPAddresses, EmailAddresses, URIs bool
|
|
}
|
|
|
|
// OID is our own version of asn1's ObjectIdentifier, so we can define a custom
|
|
// JSON marshal / unmarshal.
|
|
type OID asn1.ObjectIdentifier
|
|
|
|
// CertificatePolicy represents the ASN.1 PolicyInformation structure from
|
|
// https://tools.ietf.org/html/rfc3280.html#page-106.
|
|
// Valid values of Type are "id-qt-unotice" and "id-qt-cps"
|
|
type CertificatePolicy struct {
|
|
ID OID
|
|
Qualifiers []CertificatePolicyQualifier
|
|
}
|
|
|
|
// CertificatePolicyQualifier represents a single qualifier from an ASN.1
|
|
// PolicyInformation structure.
|
|
type CertificatePolicyQualifier struct {
|
|
Type string
|
|
Value string
|
|
}
|
|
|
|
// AuthRemote is an authenticated remote signer.
|
|
type AuthRemote struct {
|
|
RemoteName string `json:"remote"`
|
|
AuthKeyName string `json:"auth_key"`
|
|
}
|
|
|
|
// CAConstraint specifies various CA constraints on the signed certificate.
|
|
// CAConstraint would verify against (and override) the CA
|
|
// extensions in the given CSR.
|
|
type CAConstraint struct {
|
|
IsCA bool `json:"is_ca"`
|
|
MaxPathLen int `json:"max_path_len"`
|
|
MaxPathLenZero bool `json:"max_path_len_zero"`
|
|
}
|
|
|
|
// A SigningProfile stores information that the CA needs to store
|
|
// signature policy.
|
|
type SigningProfile struct {
|
|
Usage []string `json:"usages"`
|
|
IssuerURL []string `json:"issuer_urls"`
|
|
OCSP string `json:"ocsp_url"`
|
|
CRL string `json:"crl_url"`
|
|
CAConstraint CAConstraint `json:"ca_constraint"`
|
|
OCSPNoCheck bool `json:"ocsp_no_check"`
|
|
ExpiryString string `json:"expiry"`
|
|
BackdateString string `json:"backdate"`
|
|
AuthKeyName string `json:"auth_key"`
|
|
RemoteName string `json:"remote"`
|
|
NotBefore time.Time `json:"not_before"`
|
|
NotAfter time.Time `json:"not_after"`
|
|
NameWhitelistString string `json:"name_whitelist"`
|
|
AuthRemote AuthRemote `json:"auth_remote"`
|
|
CTLogServers []string `json:"ct_log_servers"`
|
|
AllowedExtensions []OID `json:"allowed_extensions"`
|
|
CertStore string `json:"cert_store"`
|
|
|
|
Policies []CertificatePolicy
|
|
Expiry time.Duration
|
|
Backdate time.Duration
|
|
Provider auth.Provider
|
|
RemoteProvider auth.Provider
|
|
RemoteServer string
|
|
RemoteCAs *x509.CertPool
|
|
ClientCert *tls.Certificate
|
|
CSRWhitelist *CSRWhitelist
|
|
NameWhitelist *regexp.Regexp
|
|
ExtensionWhitelist map[string]bool
|
|
ClientProvidesSerialNumbers bool
|
|
}
|
|
|
|
// UnmarshalJSON unmarshals a JSON string into an OID.
|
|
func (oid *OID) UnmarshalJSON(data []byte) (err error) {
|
|
if data[0] != '"' || data[len(data)-1] != '"' {
|
|
return errors.New("OID JSON string not wrapped in quotes." + string(data))
|
|
}
|
|
data = data[1 : len(data)-1]
|
|
parsedOid, err := parseObjectIdentifier(string(data))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*oid = OID(parsedOid)
|
|
return
|
|
}
|
|
|
|
// MarshalJSON marshals an oid into a JSON string.
|
|
func (oid OID) MarshalJSON() ([]byte, error) {
|
|
return []byte(fmt.Sprintf(`"%v"`, asn1.ObjectIdentifier(oid))), nil
|
|
}
|
|
|
|
func parseObjectIdentifier(oidString string) (oid asn1.ObjectIdentifier, err error) {
|
|
validOID, err := regexp.MatchString("\\d(\\.\\d+)*", oidString)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if !validOID {
|
|
err = errors.New("Invalid OID")
|
|
return
|
|
}
|
|
|
|
segments := strings.Split(oidString, ".")
|
|
oid = make(asn1.ObjectIdentifier, len(segments))
|
|
for i, intString := range segments {
|
|
oid[i], err = strconv.Atoi(intString)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
const timeFormat = "2006-01-02T15:04:05"
|
|
|
|
// populate is used to fill in the fields that are not in JSON
|
|
//
|
|
// First, the ExpiryString parameter is needed to parse
|
|
// expiration timestamps from JSON. The JSON decoder is not able to
|
|
// decode a string time duration to a time.Duration, so this is called
|
|
// when loading the configuration to properly parse and fill out the
|
|
// Expiry parameter.
|
|
// This function is also used to create references to the auth key
|
|
// and default remote for the profile.
|
|
// It returns true if ExpiryString is a valid representation of a
|
|
// time.Duration, and the AuthKeyString and RemoteName point to
|
|
// valid objects. It returns false otherwise.
|
|
func (p *SigningProfile) populate(cfg *Config) error {
|
|
if p == nil {
|
|
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("can't parse nil profile"))
|
|
}
|
|
|
|
var err error
|
|
if p.RemoteName == "" && p.AuthRemote.RemoteName == "" {
|
|
log.Debugf("parse expiry in profile")
|
|
if p.ExpiryString == "" {
|
|
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("empty expiry string"))
|
|
}
|
|
|
|
dur, err := time.ParseDuration(p.ExpiryString)
|
|
if err != nil {
|
|
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err)
|
|
}
|
|
|
|
log.Debugf("expiry is valid")
|
|
p.Expiry = dur
|
|
|
|
if p.BackdateString != "" {
|
|
dur, err = time.ParseDuration(p.BackdateString)
|
|
if err != nil {
|
|
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err)
|
|
}
|
|
|
|
p.Backdate = dur
|
|
}
|
|
|
|
if !p.NotBefore.IsZero() && !p.NotAfter.IsZero() && p.NotAfter.Before(p.NotBefore) {
|
|
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err)
|
|
}
|
|
|
|
if len(p.Policies) > 0 {
|
|
for _, policy := range p.Policies {
|
|
for _, qualifier := range policy.Qualifiers {
|
|
if qualifier.Type != "" && qualifier.Type != "id-qt-unotice" && qualifier.Type != "id-qt-cps" {
|
|
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
|
|
errors.New("invalid policy qualifier type"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if p.RemoteName != "" {
|
|
log.Debug("match remote in profile to remotes section")
|
|
if p.AuthRemote.RemoteName != "" {
|
|
log.Error("profile has both a remote and an auth remote specified")
|
|
return cferr.New(cferr.PolicyError, cferr.InvalidPolicy)
|
|
}
|
|
if remote := cfg.Remotes[p.RemoteName]; remote != "" {
|
|
if err := p.updateRemote(remote); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
|
|
errors.New("failed to find remote in remotes section"))
|
|
}
|
|
} else {
|
|
log.Debug("match auth remote in profile to remotes section")
|
|
if remote := cfg.Remotes[p.AuthRemote.RemoteName]; remote != "" {
|
|
if err := p.updateRemote(remote); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
|
|
errors.New("failed to find remote in remotes section"))
|
|
}
|
|
}
|
|
|
|
if p.AuthKeyName != "" {
|
|
log.Debug("match auth key in profile to auth_keys section")
|
|
if key, ok := cfg.AuthKeys[p.AuthKeyName]; ok == true {
|
|
if key.Type == "standard" {
|
|
p.Provider, err = auth.New(key.Key, nil)
|
|
if err != nil {
|
|
log.Debugf("failed to create new standard auth provider: %v", err)
|
|
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
|
|
errors.New("failed to create new standard auth provider"))
|
|
}
|
|
} else {
|
|
log.Debugf("unknown authentication type %v", key.Type)
|
|
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
|
|
errors.New("unknown authentication type"))
|
|
}
|
|
} else {
|
|
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
|
|
errors.New("failed to find auth_key in auth_keys section"))
|
|
}
|
|
}
|
|
|
|
if p.AuthRemote.AuthKeyName != "" {
|
|
log.Debug("match auth remote key in profile to auth_keys section")
|
|
if key, ok := cfg.AuthKeys[p.AuthRemote.AuthKeyName]; ok == true {
|
|
if key.Type == "standard" {
|
|
p.RemoteProvider, err = auth.New(key.Key, nil)
|
|
if err != nil {
|
|
log.Debugf("failed to create new standard auth provider: %v", err)
|
|
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
|
|
errors.New("failed to create new standard auth provider"))
|
|
}
|
|
} else {
|
|
log.Debugf("unknown authentication type %v", key.Type)
|
|
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
|
|
errors.New("unknown authentication type"))
|
|
}
|
|
} else {
|
|
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
|
|
errors.New("failed to find auth_remote's auth_key in auth_keys section"))
|
|
}
|
|
}
|
|
|
|
if p.NameWhitelistString != "" {
|
|
log.Debug("compiling whitelist regular expression")
|
|
rule, err := regexp.Compile(p.NameWhitelistString)
|
|
if err != nil {
|
|
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
|
|
errors.New("failed to compile name whitelist section"))
|
|
}
|
|
p.NameWhitelist = rule
|
|
}
|
|
|
|
p.ExtensionWhitelist = map[string]bool{}
|
|
for _, oid := range p.AllowedExtensions {
|
|
p.ExtensionWhitelist[asn1.ObjectIdentifier(oid).String()] = true
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// updateRemote takes a signing profile and initializes the remote server object
|
|
// to the hostname:port combination sent by remote.
|
|
func (p *SigningProfile) updateRemote(remote string) error {
|
|
if remote != "" {
|
|
p.RemoteServer = remote
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// OverrideRemotes takes a signing configuration and updates the remote server object
|
|
// to the hostname:port combination sent by remote
|
|
func (p *Signing) OverrideRemotes(remote string) error {
|
|
if remote != "" {
|
|
var err error
|
|
for _, profile := range p.Profiles {
|
|
err = profile.updateRemote(remote)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
err = p.Default.updateRemote(remote)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetClientCertKeyPairFromFile updates the properties to set client certificates for mutual
|
|
// authenticated TLS remote requests
|
|
func (p *Signing) SetClientCertKeyPairFromFile(certFile string, keyFile string) error {
|
|
if certFile != "" && keyFile != "" {
|
|
cert, err := helpers.LoadClientCertificate(certFile, keyFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, profile := range p.Profiles {
|
|
profile.ClientCert = cert
|
|
}
|
|
p.Default.ClientCert = cert
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetRemoteCAsFromFile reads root CAs from file and updates the properties to set remote CAs for TLS
|
|
// remote requests
|
|
func (p *Signing) SetRemoteCAsFromFile(caFile string) error {
|
|
if caFile != "" {
|
|
remoteCAs, err := helpers.LoadPEMCertPool(caFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.SetRemoteCAs(remoteCAs)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetRemoteCAs updates the properties to set remote CAs for TLS
|
|
// remote requests
|
|
func (p *Signing) SetRemoteCAs(remoteCAs *x509.CertPool) {
|
|
for _, profile := range p.Profiles {
|
|
profile.RemoteCAs = remoteCAs
|
|
}
|
|
p.Default.RemoteCAs = remoteCAs
|
|
}
|
|
|
|
// NeedsRemoteSigner returns true if one of the profiles has a remote set
|
|
func (p *Signing) NeedsRemoteSigner() bool {
|
|
for _, profile := range p.Profiles {
|
|
if profile.RemoteServer != "" {
|
|
return true
|
|
}
|
|
}
|
|
|
|
if p.Default.RemoteServer != "" {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// NeedsLocalSigner returns true if one of the profiles doe not have a remote set
|
|
func (p *Signing) NeedsLocalSigner() bool {
|
|
for _, profile := range p.Profiles {
|
|
if profile.RemoteServer == "" {
|
|
return true
|
|
}
|
|
}
|
|
|
|
if p.Default.RemoteServer == "" {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Usages parses the list of key uses in the profile, translating them
|
|
// to a list of X.509 key usages and extended key usages. The unknown
|
|
// uses are collected into a slice that is also returned.
|
|
func (p *SigningProfile) Usages() (ku x509.KeyUsage, eku []x509.ExtKeyUsage, unk []string) {
|
|
for _, keyUse := range p.Usage {
|
|
if kuse, ok := KeyUsage[keyUse]; ok {
|
|
ku |= kuse
|
|
} else if ekuse, ok := ExtKeyUsage[keyUse]; ok {
|
|
eku = append(eku, ekuse)
|
|
} else {
|
|
unk = append(unk, keyUse)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// A valid profile must be a valid local profile or a valid remote profile.
|
|
// A valid local profile has defined at least key usages to be used, and a
|
|
// valid local default profile has defined at least a default expiration.
|
|
// A valid remote profile (default or not) has remote signer initialized.
|
|
// In addition, a remote profile must has a valid auth provider if auth
|
|
// key defined.
|
|
func (p *SigningProfile) validProfile(isDefault bool) bool {
|
|
if p == nil {
|
|
return false
|
|
}
|
|
|
|
if p.AuthRemote.RemoteName == "" && p.AuthRemote.AuthKeyName != "" {
|
|
log.Debugf("invalid auth remote profile: no remote signer specified")
|
|
return false
|
|
}
|
|
|
|
if p.RemoteName != "" {
|
|
log.Debugf("validate remote profile")
|
|
|
|
if p.RemoteServer == "" {
|
|
log.Debugf("invalid remote profile: no remote signer specified")
|
|
return false
|
|
}
|
|
|
|
if p.AuthKeyName != "" && p.Provider == nil {
|
|
log.Debugf("invalid remote profile: auth key name is defined but no auth provider is set")
|
|
return false
|
|
}
|
|
|
|
if p.AuthRemote.RemoteName != "" {
|
|
log.Debugf("invalid remote profile: auth remote is also specified")
|
|
return false
|
|
}
|
|
} else if p.AuthRemote.RemoteName != "" {
|
|
log.Debugf("validate auth remote profile")
|
|
if p.RemoteServer == "" {
|
|
log.Debugf("invalid auth remote profile: no remote signer specified")
|
|
return false
|
|
}
|
|
|
|
if p.AuthRemote.AuthKeyName == "" || p.RemoteProvider == nil {
|
|
log.Debugf("invalid auth remote profile: no auth key is defined")
|
|
return false
|
|
}
|
|
} else {
|
|
log.Debugf("validate local profile")
|
|
if !isDefault {
|
|
if len(p.Usage) == 0 {
|
|
log.Debugf("invalid local profile: no usages specified")
|
|
return false
|
|
} else if _, _, unk := p.Usages(); len(unk) == len(p.Usage) {
|
|
log.Debugf("invalid local profile: no valid usages")
|
|
return false
|
|
}
|
|
} else {
|
|
if p.Expiry == 0 {
|
|
log.Debugf("invalid local profile: no expiry set")
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Debugf("profile is valid")
|
|
return true
|
|
}
|
|
|
|
// This checks if the SigningProfile object contains configurations that are only effective with a local signer
|
|
// which has access to CA private key.
|
|
func (p *SigningProfile) hasLocalConfig() bool {
|
|
if p.Usage != nil ||
|
|
p.IssuerURL != nil ||
|
|
p.OCSP != "" ||
|
|
p.ExpiryString != "" ||
|
|
p.BackdateString != "" ||
|
|
p.CAConstraint.IsCA != false ||
|
|
!p.NotBefore.IsZero() ||
|
|
!p.NotAfter.IsZero() ||
|
|
p.NameWhitelistString != "" ||
|
|
len(p.CTLogServers) != 0 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// warnSkippedSettings prints a log warning message about skipped settings
|
|
// in a SigningProfile, usually due to remote signer.
|
|
func (p *Signing) warnSkippedSettings() {
|
|
const warningMessage = `The configuration value by "usages", "issuer_urls", "ocsp_url", "crl_url", "ca_constraint", "expiry", "backdate", "not_before", "not_after", "cert_store" and "ct_log_servers" are skipped`
|
|
if p == nil {
|
|
return
|
|
}
|
|
|
|
if (p.Default.RemoteName != "" || p.Default.AuthRemote.RemoteName != "") && p.Default.hasLocalConfig() {
|
|
log.Warning("default profile points to a remote signer: ", warningMessage)
|
|
}
|
|
|
|
for name, profile := range p.Profiles {
|
|
if (profile.RemoteName != "" || profile.AuthRemote.RemoteName != "") && profile.hasLocalConfig() {
|
|
log.Warningf("Profiles[%s] points to a remote signer: %s", name, warningMessage)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Signing codifies the signature configuration policy for a CA.
|
|
type Signing struct {
|
|
Profiles map[string]*SigningProfile `json:"profiles"`
|
|
Default *SigningProfile `json:"default"`
|
|
}
|
|
|
|
// Config stores configuration information for the CA.
|
|
type Config struct {
|
|
Signing *Signing `json:"signing"`
|
|
OCSP *ocspConfig.Config `json:"ocsp"`
|
|
AuthKeys map[string]AuthKey `json:"auth_keys,omitempty"`
|
|
Remotes map[string]string `json:"remotes,omitempty"`
|
|
}
|
|
|
|
// Valid ensures that Config is a valid configuration. It should be
|
|
// called immediately after parsing a configuration file.
|
|
func (c *Config) Valid() bool {
|
|
return c.Signing.Valid()
|
|
}
|
|
|
|
// Valid checks the signature policies, ensuring they are valid
|
|
// policies. A policy is valid if it has defined at least key usages
|
|
// to be used, and a valid default profile has defined at least a
|
|
// default expiration.
|
|
func (p *Signing) Valid() bool {
|
|
if p == nil {
|
|
return false
|
|
}
|
|
|
|
log.Debugf("validating configuration")
|
|
if !p.Default.validProfile(true) {
|
|
log.Debugf("default profile is invalid")
|
|
return false
|
|
}
|
|
|
|
for _, sp := range p.Profiles {
|
|
if !sp.validProfile(false) {
|
|
log.Debugf("invalid profile")
|
|
return false
|
|
}
|
|
}
|
|
|
|
p.warnSkippedSettings()
|
|
|
|
return true
|
|
}
|
|
|
|
// KeyUsage contains a mapping of string names to key usages.
|
|
var KeyUsage = map[string]x509.KeyUsage{
|
|
"signing": x509.KeyUsageDigitalSignature,
|
|
"digital signature": x509.KeyUsageDigitalSignature,
|
|
"content commitment": x509.KeyUsageContentCommitment,
|
|
"key encipherment": x509.KeyUsageKeyEncipherment,
|
|
"key agreement": x509.KeyUsageKeyAgreement,
|
|
"data encipherment": x509.KeyUsageDataEncipherment,
|
|
"cert sign": x509.KeyUsageCertSign,
|
|
"crl sign": x509.KeyUsageCRLSign,
|
|
"encipher only": x509.KeyUsageEncipherOnly,
|
|
"decipher only": x509.KeyUsageDecipherOnly,
|
|
}
|
|
|
|
// ExtKeyUsage contains a mapping of string names to extended key
|
|
// usages.
|
|
var ExtKeyUsage = map[string]x509.ExtKeyUsage{
|
|
"any": x509.ExtKeyUsageAny,
|
|
"server auth": x509.ExtKeyUsageServerAuth,
|
|
"client auth": x509.ExtKeyUsageClientAuth,
|
|
"code signing": x509.ExtKeyUsageCodeSigning,
|
|
"email protection": x509.ExtKeyUsageEmailProtection,
|
|
"s/mime": x509.ExtKeyUsageEmailProtection,
|
|
"ipsec end system": x509.ExtKeyUsageIPSECEndSystem,
|
|
"ipsec tunnel": x509.ExtKeyUsageIPSECTunnel,
|
|
"ipsec user": x509.ExtKeyUsageIPSECUser,
|
|
"timestamping": x509.ExtKeyUsageTimeStamping,
|
|
"ocsp signing": x509.ExtKeyUsageOCSPSigning,
|
|
"microsoft sgc": x509.ExtKeyUsageMicrosoftServerGatedCrypto,
|
|
"netscape sgc": x509.ExtKeyUsageNetscapeServerGatedCrypto,
|
|
}
|
|
|
|
// An AuthKey contains an entry for a key used for authentication.
|
|
type AuthKey struct {
|
|
// Type contains information needed to select the appropriate
|
|
// constructor. For example, "standard" for HMAC-SHA-256,
|
|
// "standard-ip" for HMAC-SHA-256 incorporating the client's
|
|
// IP.
|
|
Type string `json:"type"`
|
|
// Key contains the key information, such as a hex-encoded
|
|
// HMAC key.
|
|
Key string `json:"key"`
|
|
}
|
|
|
|
// DefaultConfig returns a default configuration specifying basic key
|
|
// usage and a 1 year expiration time. The key usages chosen are
|
|
// signing, key encipherment, client auth and server auth.
|
|
func DefaultConfig() *SigningProfile {
|
|
d := helpers.OneYear
|
|
return &SigningProfile{
|
|
Usage: []string{"signing", "key encipherment", "server auth", "client auth"},
|
|
Expiry: d,
|
|
ExpiryString: "8760h",
|
|
}
|
|
}
|
|
|
|
// LoadFile attempts to load the configuration file stored at the path
|
|
// and returns the configuration. On error, it returns nil.
|
|
func LoadFile(path string) (*Config, error) {
|
|
log.Debugf("loading configuration file from %s", path)
|
|
if path == "" {
|
|
return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("invalid path"))
|
|
}
|
|
|
|
body, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("could not read configuration file"))
|
|
}
|
|
|
|
return LoadConfig(body)
|
|
}
|
|
|
|
// LoadConfig attempts to load the configuration from a byte slice.
|
|
// On error, it returns nil.
|
|
func LoadConfig(config []byte) (*Config, error) {
|
|
var cfg = &Config{}
|
|
err := json.Unmarshal(config, &cfg)
|
|
if err != nil {
|
|
return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
|
|
errors.New("failed to unmarshal configuration: "+err.Error()))
|
|
}
|
|
|
|
if cfg.Signing == nil {
|
|
return nil, errors.New("No \"signing\" field present")
|
|
}
|
|
|
|
if cfg.Signing.Default == nil {
|
|
log.Debugf("no default given: using default config")
|
|
cfg.Signing.Default = DefaultConfig()
|
|
} else {
|
|
if err := cfg.Signing.Default.populate(cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
for k := range cfg.Signing.Profiles {
|
|
if err := cfg.Signing.Profiles[k].populate(cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if !cfg.Valid() {
|
|
return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("invalid configuration"))
|
|
}
|
|
|
|
log.Debugf("configuration ok")
|
|
return cfg, nil
|
|
}
|