| // Copyright 2017 The LUCI Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package tokensigning |
| |
| import ( |
| "context" |
| "encoding/base64" |
| |
| "google.golang.org/protobuf/proto" |
| |
| "go.chromium.org/luci/common/retry/transient" |
| "go.chromium.org/luci/server/auth/signing" |
| ) |
| |
| // Signer knows how to sign protos and serialize/encode signed result. |
| type Signer struct { |
| // Signer is the actual signer: it knows how to sign blobs. |
| Signer signing.Signer |
| |
| // SigningContext is prepended to the token blob before it is signed. |
| // |
| // It exists to avoid cross-protocol attacks, when same key is used to sign |
| // different kinds of tokens. An attacker may get a token of kind A, and use |
| // it in place of a token of kind B. This may produce unexpected (possibly |
| // bad) results, especially for proto-serialized tokens (that all use small |
| // integers for message tags). |
| // |
| // By using different SigningContext strings per token kind we ensure tokens |
| // are recognized as correctly signed only when they are used in an |
| // appropriate context. |
| // |
| // SigningContext should be some arbitrary constant string that designates the |
| // usage of the token. We actually prepend SigningContext + '\x00' to the |
| // token blob. |
| // |
| // If SigningContext is "", this mechanism is completely skipped. |
| SigningContext string |
| |
| // Encoding is base64 encoding to use (or RawURLEncoding if nil). |
| Encoding *base64.Encoding |
| |
| // Wrap takes a blob with token body, the signature and signing key details |
| // and returns a proto to serialize/encode and return as the final signed |
| // token. |
| Wrap func(unwrapped *Unwrapped) proto.Message |
| } |
| |
| // SignToken serializes the body, signs it and returns serialized envelope. |
| // |
| // Produces base64 URL-safe token or an error (possibly transient). |
| func (s *Signer) SignToken(c context.Context, body proto.Message) (string, error) { |
| info, err := s.Signer.ServiceInfo(c) |
| if err != nil { |
| return "", transient.Tag.Apply(err) |
| } |
| blob, err := proto.Marshal(body) |
| if err != nil { |
| return "", err |
| } |
| withCtx := prependSigningContext(blob, s.SigningContext) |
| keyID, sig, err := s.Signer.SignBytes(c, withCtx) |
| if err != nil { |
| return "", transient.Tag.Apply(err) |
| } |
| tok, err := proto.Marshal(s.Wrap(&Unwrapped{ |
| Body: blob, |
| RsaSHA256Sig: sig, |
| SignerID: info.ServiceAccountName, |
| KeyID: keyID, |
| })) |
| if err != nil { |
| return "", err |
| } |
| enc := s.Encoding |
| if enc == nil { |
| enc = base64.RawURLEncoding |
| } |
| return enc.EncodeToString(tok), nil |
| } |