github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/utils/keys/policy.go (about)

     1  /*
     2  Copyright 2022 Gravitational, Inc.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6      http://www.apache.org/licenses/LICENSE-2.0
     7  Unless required by applicable law or agreed to in writing, software
     8  distributed under the License is distributed on an "AS IS" BASIS,
     9  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    10  See the License for the specific language governing permissions and
    11  limitations under the License.
    12  */
    13  
    14  package keys
    15  
    16  import (
    17  	"fmt"
    18  	"regexp"
    19  
    20  	"github.com/gravitational/trace"
    21  )
    22  
    23  // PrivateKeyPolicy is a requirement for client private key storage.
    24  type PrivateKeyPolicy string
    25  
    26  const (
    27  	// PrivateKeyPolicyNone means that the client can store their private keys
    28  	// anywhere (usually on disk).
    29  	PrivateKeyPolicyNone PrivateKeyPolicy = "none"
    30  	// PrivateKeyPolicyHardwareKey means that the client must use a valid
    31  	// hardware key to generate and store their private keys securely.
    32  	PrivateKeyPolicyHardwareKey PrivateKeyPolicy = "hardware_key"
    33  	// PrivateKeyPolicyHardwareKeyTouch means that the client must use a valid
    34  	// hardware key to generate and store their private keys securely, and
    35  	// this key must require touch to be accessed and used.
    36  	PrivateKeyPolicyHardwareKeyTouch PrivateKeyPolicy = "hardware_key_touch"
    37  	// PrivateKeyPolicyHardwareKeyPIN means that the client must use a valid
    38  	// hardware key to generate and store their private keys securely, and
    39  	// this key must require pin to be accessed and used.
    40  	PrivateKeyPolicyHardwareKeyPIN PrivateKeyPolicy = "hardware_key_pin"
    41  	// PrivateKeyPolicyHardwareKeyTouchAndPIN means that the client must use a valid
    42  	// hardware key to generate and store their private keys securely, and
    43  	// this key must require touch and pin to be accessed and used.
    44  	PrivateKeyPolicyHardwareKeyTouchAndPIN PrivateKeyPolicy = "hardware_key_touch_and_pin"
    45  	// PrivateKeyPolicyWebSession is a special case used for Web Sessions. This policy
    46  	// implies that the client private key and certificate are stored in the Proxy
    47  	// Process Memory and Auth Storage. These certs do not leave the Proxy/Auth
    48  	// services, but the Web Client receives a Web Cookie which can be used to
    49  	// make requests with the server-side client key+cert.
    50  	//
    51  	// This policy does not provide the same hardware key guarantee as the above policies.
    52  	// Instead, this policy must be accompanied by WebAuthn prompts for important operations
    53  	// in order to pass hardware key policy requirements.
    54  	PrivateKeyPolicyWebSession PrivateKeyPolicy = "web_session"
    55  )
    56  
    57  // IsSatisfiedBy returns whether this key policy is satisfied by the given key policy.
    58  func (requiredPolicy PrivateKeyPolicy) IsSatisfiedBy(keyPolicy PrivateKeyPolicy) bool {
    59  	// Web sessions are treated as a special case that meets all private key policy requirements.
    60  	if keyPolicy == PrivateKeyPolicyWebSession {
    61  		return true
    62  	}
    63  
    64  	switch requiredPolicy {
    65  	case PrivateKeyPolicyNone:
    66  		return true
    67  	case PrivateKeyPolicyHardwareKey:
    68  		return keyPolicy.IsHardwareKeyPolicy()
    69  	case PrivateKeyPolicyHardwareKeyTouch:
    70  		return keyPolicy.isHardwareKeyTouchVerified()
    71  	case PrivateKeyPolicyHardwareKeyPIN:
    72  		return keyPolicy.isHardwareKeyPINVerified()
    73  	case PrivateKeyPolicyHardwareKeyTouchAndPIN:
    74  		return keyPolicy.isHardwareKeyTouchVerified() && keyPolicy.isHardwareKeyPINVerified()
    75  	}
    76  
    77  	return false
    78  }
    79  
    80  func (p PrivateKeyPolicy) isHardwareKeyTouchVerified() bool {
    81  	switch p {
    82  	case PrivateKeyPolicyHardwareKeyTouch, PrivateKeyPolicyHardwareKeyTouchAndPIN:
    83  		return true
    84  	}
    85  	return false
    86  }
    87  
    88  func (p PrivateKeyPolicy) isHardwareKeyPINVerified() bool {
    89  	switch p {
    90  	case PrivateKeyPolicyHardwareKeyPIN, PrivateKeyPolicyHardwareKeyTouchAndPIN:
    91  		return true
    92  	}
    93  	return false
    94  }
    95  
    96  // Deprecated in favor of IsSatisfiedBy.
    97  // TODO(Joerger): delete once reference in /e is replaced.
    98  func (requiredPolicy PrivateKeyPolicy) VerifyPolicy(keyPolicy PrivateKeyPolicy) error {
    99  	if !requiredPolicy.IsSatisfiedBy(keyPolicy) {
   100  		return NewPrivateKeyPolicyError(requiredPolicy)
   101  	}
   102  	return nil
   103  }
   104  
   105  // IsHardwareKeyPolicy return true if this private key policy requires a hardware key.
   106  func (p PrivateKeyPolicy) IsHardwareKeyPolicy() bool {
   107  	switch p {
   108  	case PrivateKeyPolicyHardwareKey,
   109  		PrivateKeyPolicyHardwareKeyTouch,
   110  		PrivateKeyPolicyHardwareKeyPIN,
   111  		PrivateKeyPolicyHardwareKeyTouchAndPIN:
   112  		return true
   113  	}
   114  	return false
   115  }
   116  
   117  // MFAVerified checks that private keys with this key policy count as MFA verified.
   118  // Both Hardware key touch and pin are count as MFA verification.
   119  //
   120  // Note: MFA checks with private key policies are only performed during the establishment
   121  // of the connection, during the TLS/SSH handshake. For long term connections, MFA should
   122  // be re-verified through other methods (e.g. webauthn).
   123  func (p PrivateKeyPolicy) MFAVerified() bool {
   124  	return p.isHardwareKeyTouchVerified() || p.isHardwareKeyPINVerified()
   125  }
   126  
   127  func (p PrivateKeyPolicy) validate() error {
   128  	switch p {
   129  	case PrivateKeyPolicyNone,
   130  		PrivateKeyPolicyHardwareKey,
   131  		PrivateKeyPolicyHardwareKeyTouch,
   132  		PrivateKeyPolicyHardwareKeyPIN,
   133  		PrivateKeyPolicyHardwareKeyTouchAndPIN,
   134  		PrivateKeyPolicyWebSession:
   135  		return nil
   136  	}
   137  	return trace.BadParameter("%q is not a valid key policy", p)
   138  }
   139  
   140  // PolicyThatSatisfiesSet returns least restrictive policy necessary to satisfy the given set of policies.
   141  func PolicyThatSatisfiesSet(policies []PrivateKeyPolicy) (PrivateKeyPolicy, error) {
   142  	setPolicy := PrivateKeyPolicyNone
   143  	for _, policy := range policies {
   144  		if policy.IsSatisfiedBy(setPolicy) {
   145  			continue
   146  		}
   147  
   148  		switch {
   149  		case setPolicy.IsSatisfiedBy(policy):
   150  			// Upgrade set policy to stricter policy.
   151  			setPolicy = policy
   152  
   153  		case policy.IsSatisfiedBy(PrivateKeyPolicyHardwareKeyTouchAndPIN) &&
   154  			setPolicy.IsSatisfiedBy(PrivateKeyPolicyHardwareKeyTouchAndPIN):
   155  			// Neither policy is met by the other (pin or touch), but both are met by
   156  			// stricter pin+touch policy.
   157  			setPolicy = PrivateKeyPolicyHardwareKeyTouchAndPIN
   158  
   159  		default:
   160  			// Currently, "hardware_key_touch_and_pin" is the strictest policy available and
   161  			// meets every other required policy. However, in the future we may add policy
   162  			// requirements that are mutually exclusive, so this logic is future proofed.
   163  			return PrivateKeyPolicyNone, trace.BadParameter(""+
   164  				"private key policy requirements %q and %q are incompatible, "+
   165  				"please contact the cluster administrator", policy, setPolicy)
   166  		}
   167  	}
   168  
   169  	return setPolicy, nil
   170  }
   171  
   172  var privateKeyPolicyErrRegex = regexp.MustCompile(`private key policy not (met|satisfied): (\w+)`)
   173  
   174  func NewPrivateKeyPolicyError(p PrivateKeyPolicy) error {
   175  	// TODO(Joerger): Replace with "private key policy not satisfied" in 16.0.0
   176  	return trace.BadParameter(fmt.Sprintf("private key policy not met: %s", p))
   177  }
   178  
   179  // ParsePrivateKeyPolicyError checks if the given error is a private key policy
   180  // error and returns the contained unsatisfied PrivateKeyPolicy.
   181  func ParsePrivateKeyPolicyError(err error) (PrivateKeyPolicy, error) {
   182  	// subMatches should have two groups - the full string and the policy "(\w+)"
   183  	subMatches := privateKeyPolicyErrRegex.FindStringSubmatch(err.Error())
   184  	if subMatches == nil || len(subMatches) != 3 {
   185  		return "", trace.BadParameter("provided error is not a key policy error")
   186  	}
   187  
   188  	policy := PrivateKeyPolicy(subMatches[2])
   189  	if err := policy.validate(); err != nil {
   190  		return "", trace.Wrap(err)
   191  	}
   192  	return policy, nil
   193  }
   194  
   195  // IsPrivateKeyPolicyError returns true if the given error is a private key policy error.
   196  func IsPrivateKeyPolicyError(err error) bool {
   197  	if err == nil {
   198  		return false
   199  	}
   200  	return privateKeyPolicyErrRegex.MatchString(err.Error())
   201  }