go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/tokenserver/appengine/impl/utils/tokensigning/inspector.go (about) 1 // Copyright 2017 The LUCI Authors. 2 // 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 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package tokensigning 16 17 import ( 18 "context" 19 "crypto/x509" 20 "encoding/base64" 21 "fmt" 22 "strings" 23 24 "google.golang.org/protobuf/proto" 25 26 "go.chromium.org/luci/common/clock" 27 "go.chromium.org/luci/common/retry/transient" 28 "go.chromium.org/luci/server/auth/signing" 29 ) 30 31 // Inspector knows how to inspect tokens produced by Signer. 32 // 33 // It is used by Inspect<something>Token RPCs (available only to admins). It 34 // tries to return as much information as possible. In particular, it tries to 35 // deserialize the token body even if the signature is no longer valid. This is 36 // useful when debugging broken tokens. 37 // 38 // Since it is available only to admins, we assume the possibility of abuse is 39 // small. 40 type Inspector struct { 41 // Certificates returns certs bundle used to validate the token signature. 42 Certificates CertificatesSupplier 43 44 // Encoding is base64 encoding to used for token (or RawURLEncoding if nil). 45 Encoding *base64.Encoding 46 47 // SigningContext is prepended to the token blob before signature check. 48 // 49 // See SigningContext in Signer struct for more info. 50 SigningContext string 51 52 // Envelope returns an empty message of same type as produced by signer.Wrap. 53 Envelope func() proto.Message 54 55 // Body returns an empty messages corresponding to the token body type. 56 Body func() proto.Message 57 58 // Unwrap extracts information from envelope proto message. 59 // 60 // It must set Body, RsaSHA256Sig and KeyID fields. 61 Unwrap func(e proto.Message) Unwrapped 62 63 // Lifespan extracts a lifespan from the deserialized body of the token. 64 Lifespan func(e proto.Message) Lifespan 65 } 66 67 // CertificatesSupplier produces signing.PublicCertificates. 68 type CertificatesSupplier interface { 69 // Certificates returns certs bundle used to validate the token signature. 70 Certificates(c context.Context) (*signing.PublicCertificates, error) 71 } 72 73 // Inspection is the result of token inspection. 74 type Inspection struct { 75 Signed bool // true if the token is structurally valid and signed 76 NonExpired bool // true if the token hasn't expire yet (may be bogus for unsigned tokens) 77 InvalidityReason string // human readable reason why the token is invalid or "" if it is valid 78 Envelope proto.Message // deserialized token envelope 79 Body proto.Message // deserialized token body 80 } 81 82 // InspectToken extracts as much information as possible from the token. 83 // 84 // Returns errors only if the inspection operation itself fails (i.e we can't 85 // determine whether the token valid or not). If the given token is invalid, 86 // returns Inspection object with details and nil error. 87 func (i *Inspector) InspectToken(c context.Context, tok string) (*Inspection, error) { 88 res := &Inspection{} 89 90 enc := i.Encoding 91 if enc == nil { 92 enc = base64.RawURLEncoding 93 } 94 95 // Byte blob with serialized envelope. 96 blob, err := enc.DecodeString(tok) 97 if err != nil { 98 res.InvalidityReason = fmt.Sprintf("not base64 - %s", err) 99 return res, nil 100 } 101 102 // Deserialize the envelope into a proto message. 103 env := i.Envelope() 104 if err = proto.Unmarshal(blob, env); err != nil { 105 res.InvalidityReason = fmt.Sprintf("can't unmarshal the envelope - %s", err) 106 return res, nil 107 } 108 res.Envelope = env 109 110 // Convert opaque proto message into a struct we can work with. 111 unwrapped := i.Unwrap(res.Envelope) 112 113 // Try to deserialize the body, if possible. 114 body := i.Body() 115 if err = proto.Unmarshal(unwrapped.Body, body); err != nil { 116 res.InvalidityReason = fmt.Sprintf("can't unmarshal the token body - %s", err) 117 return res, nil 118 } 119 res.Body = body 120 121 var reasons []string 122 123 if reason := i.checkLifetime(c, body); reason != "" { 124 reasons = append(reasons, reason) 125 } else { 126 res.NonExpired = true 127 } 128 129 switch reason, err := i.checkSignature(c, &unwrapped); { 130 case err != nil: 131 return nil, err 132 case reason != "": 133 reasons = append(reasons, reason) 134 default: 135 res.Signed = true 136 } 137 138 res.InvalidityReason = strings.Join(reasons, "; ") 139 return res, nil 140 } 141 142 // checkLifetime checks that token has not expired yet. 143 // 144 // Returns "" if it hasn't expire yet, or an invalidity reason if it has. 145 func (i *Inspector) checkLifetime(c context.Context, body proto.Message) string { 146 lifespan := i.Lifespan(body) 147 now := clock.Now(c) 148 switch { 149 case lifespan.NotAfter == lifespan.NotBefore: 150 return "can't extract the token lifespan" 151 case now.Before(lifespan.NotBefore): 152 return "not active yet" 153 case now.After(lifespan.NotAfter): 154 return "expired" 155 default: 156 return "" 157 } 158 } 159 160 // checkSignature verifies the signature of the token. 161 // 162 // Returns "" if the signature is correct, or an invalidity reason if it is not. 163 func (i *Inspector) checkSignature(c context.Context, unwrapped *Unwrapped) (string, error) { 164 certsBundle, err := i.Certificates.Certificates(c) 165 if err != nil { 166 return "", transient.Tag.Apply(err) 167 } 168 cert, err := certsBundle.CertificateForKey(unwrapped.KeyID) 169 if err != nil { 170 return fmt.Sprintf("invalid signing key - %s", err), nil 171 } 172 withCtx := prependSigningContext(unwrapped.Body, i.SigningContext) 173 err = cert.CheckSignature(x509.SHA256WithRSA, withCtx, unwrapped.RsaSHA256Sig) 174 if err != nil { 175 return fmt.Sprintf("bad signature - %s", err), nil 176 } 177 return "", nil 178 }