2018-06-17 07:32:44 +00:00
// 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
2019-01-21 22:44:11 +00:00
DNSNames , IPAddresses , EmailAddresses , URIs bool
2018-06-17 07:32:44 +00:00
}
// 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
}