// Copyright 2018 Google LLC // // 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 mac import ( "fmt" "github.com/google/tink/go/core/cryptofmt" "github.com/google/tink/go/core/primitiveset" "github.com/google/tink/go/internal/internalregistry" "github.com/google/tink/go/internal/monitoringutil" "github.com/google/tink/go/keyset" "github.com/google/tink/go/monitoring" "github.com/google/tink/go/tink" tinkpb "github.com/google/tink/go/proto/tink_go_proto" ) const ( intSize = 32 << (^uint(0) >> 63) // 32 or 64 maxInt = 1<<(intSize-1) - 1 ) // New creates a MAC primitive from the given keyset handle. func New(handle *keyset.Handle) (tink.MAC, error) { ps, err := handle.Primitives() if err != nil { return nil, fmt.Errorf("mac_factory: cannot obtain primitive set: %s", err) } return newWrappedMAC(ps) } // wrappedMAC is a MAC implementation that uses the underlying primitive set to compute and // verify MACs. type wrappedMAC struct { ps *primitiveset.PrimitiveSet computeLogger monitoring.Logger verifyLogger monitoring.Logger } var _ (tink.MAC) = (*wrappedMAC)(nil) func newWrappedMAC(ps *primitiveset.PrimitiveSet) (*wrappedMAC, error) { if _, ok := (ps.Primary.Primitive).(tink.MAC); !ok { return nil, fmt.Errorf("mac_factory: not a MAC primitive") } for _, primitives := range ps.Entries { for _, p := range primitives { if _, ok := (p.Primitive).(tink.MAC); !ok { return nil, fmt.Errorf("mac_factory: not an MAC primitive") } } } computeLogger, verifyLogger, err := createLoggers(ps) if err != nil { return nil, err } return &wrappedMAC{ ps: ps, computeLogger: computeLogger, verifyLogger: verifyLogger, }, nil } func createLoggers(ps *primitiveset.PrimitiveSet) (monitoring.Logger, monitoring.Logger, error) { if len(ps.Annotations) == 0 { return &monitoringutil.DoNothingLogger{}, &monitoringutil.DoNothingLogger{}, nil } client := internalregistry.GetMonitoringClient() keysetInfo, err := monitoringutil.KeysetInfoFromPrimitiveSet(ps) if err != nil { return nil, nil, err } computeLogger, err := client.NewLogger(&monitoring.Context{ Primitive: "mac", APIFunction: "compute", KeysetInfo: keysetInfo, }) if err != nil { return nil, nil, err } verifyLogger, err := client.NewLogger(&monitoring.Context{ Primitive: "mac", APIFunction: "verify", KeysetInfo: keysetInfo, }) if err != nil { return nil, nil, err } return computeLogger, verifyLogger, nil } // ComputeMAC calculates a MAC over the given data using the primary primitive // and returns the concatenation of the primary's identifier and the calculated mac. func (m *wrappedMAC) ComputeMAC(data []byte) ([]byte, error) { primary := m.ps.Primary primitive, ok := (primary.Primitive).(tink.MAC) if !ok { return nil, fmt.Errorf("mac_factory: not a MAC primitive") } if m.ps.Primary.PrefixType == tinkpb.OutputPrefixType_LEGACY { d := data if len(d) >= maxInt { m.computeLogger.LogFailure() return nil, fmt.Errorf("mac_factory: data too long") } data = make([]byte, 0, len(d)+1) data = append(data, d...) data = append(data, byte(0)) } mac, err := primitive.ComputeMAC(data) if err != nil { m.computeLogger.LogFailure() return nil, err } m.computeLogger.Log(primary.KeyID, len(data)) if len(primary.Prefix) == 0 { return mac, nil } output := make([]byte, 0, len(primary.Prefix)+len(mac)) output = append(output, primary.Prefix...) output = append(output, mac...) return output, nil } var errInvalidMAC = fmt.Errorf("mac_factory: invalid mac") // VerifyMAC verifies whether the given mac is a correct authentication code // for the given data. func (m *wrappedMAC) VerifyMAC(mac, data []byte) error { // This also rejects raw MAC with size of 4 bytes or fewer. Those MACs are // clearly insecure, thus should be discouraged. prefixSize := cryptofmt.NonRawPrefixSize if len(mac) <= prefixSize { m.verifyLogger.LogFailure() return errInvalidMAC } // try non raw keys prefix := mac[:prefixSize] macNoPrefix := mac[prefixSize:] entries, err := m.ps.EntriesForPrefix(string(prefix)) if err == nil { for i := 0; i < len(entries); i++ { entry := entries[i] p, ok := (entry.Primitive).(tink.MAC) if !ok { return fmt.Errorf("mac_factory internal error: not a MAC primitive") } if entry.PrefixType == tinkpb.OutputPrefixType_LEGACY { d := data if len(d) >= maxInt { m.verifyLogger.LogFailure() return fmt.Errorf("mac_factory: data too long") } data = make([]byte, 0, len(d)+1) data = append(data, d...) data = append(data, byte(0)) } if err = p.VerifyMAC(macNoPrefix, data); err == nil { m.verifyLogger.Log(entry.KeyID, len(data)) return nil } } } // try raw keys entries, err = m.ps.RawEntries() if err == nil { for i := 0; i < len(entries); i++ { p, ok := (entries[i].Primitive).(tink.MAC) if !ok { return fmt.Errorf("mac_factory internal error: not a MAC primitive") } if err = p.VerifyMAC(mac, data); err == nil { m.verifyLogger.Log(entries[i].KeyID, len(data)) return nil } } } // nothing worked m.verifyLogger.LogFailure() return errInvalidMAC }