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 }