github.com/cs3org/reva/v2@v2.27.7/pkg/mentix/key/apikey.go (about)

     1  // Copyright 2018-2020 CERN
     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  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package key
    20  
    21  import (
    22  	"crypto/md5"
    23  	"crypto/rand"
    24  	"fmt"
    25  	hashpkg "hash"
    26  	"strconv"
    27  	"strings"
    28  
    29  	"github.com/pkg/errors"
    30  )
    31  
    32  // APIKey is the type used to store API keys.
    33  type APIKey = string
    34  
    35  const (
    36  	// FlagDefault marks API keys for default (community) accounts.
    37  	FlagDefault = 0x0000
    38  	// FlagScienceMesh marks API keys for ScienceMesh (partner) accounts.
    39  	FlagScienceMesh = 0x0001
    40  )
    41  
    42  const (
    43  	randomStringLength = 30
    44  	apiKeyLength       = randomStringLength + 2 + 32
    45  
    46  	charset = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"
    47  )
    48  
    49  // GenerateAPIKey generates a new (random) API key which also contains flags and a (salted) hash.
    50  // An API key has the following format:
    51  // <RandomString:30><Flags:2><SaltedHash:32>
    52  func GenerateAPIKey(salt string, flags int) (APIKey, error) {
    53  	if len(salt) == 0 {
    54  		return "", errors.Errorf("no salt specified")
    55  	}
    56  
    57  	randomString, err := generateRandomString(randomStringLength)
    58  	if err != nil {
    59  		return "", errors.Wrap(err, "unable to generate API key")
    60  	}
    61  
    62  	// To verify an API key, a hash is used which contains, beside the random string and flags, the email address
    63  	hash := calculateHash(randomString, flags, salt)
    64  	return fmt.Sprintf("%s%02x%032x", randomString, flags, hash.Sum(nil)), nil
    65  }
    66  
    67  // VerifyAPIKey checks if the API key is valid given the specified salt value.
    68  func VerifyAPIKey(apiKey APIKey, salt string) error {
    69  	randomString, flags, hash, err := SplitAPIKey(apiKey)
    70  	if err != nil {
    71  		return errors.Wrap(err, "error while extracting API key information")
    72  	}
    73  
    74  	hashCalc := calculateHash(randomString, flags, salt)
    75  	if fmt.Sprintf("%032x", hashCalc.Sum(nil)) != hash {
    76  		return errors.Errorf("the API key is invalid")
    77  	}
    78  
    79  	return nil
    80  }
    81  
    82  // SplitAPIKey splits an API key into its pieces: RandomString, Flags and Hash.
    83  func SplitAPIKey(apiKey APIKey) (string, int, string, error) {
    84  	if len(apiKey) != apiKeyLength {
    85  		return "", 0, "", errors.Errorf("invalid API key length")
    86  	}
    87  
    88  	randomString := apiKey[:randomStringLength]
    89  	flags, err := strconv.Atoi(apiKey[randomStringLength : randomStringLength+2])
    90  	if err != nil {
    91  		return "", 0, "", errors.Errorf("invalid API key format")
    92  	}
    93  	hash := apiKey[randomStringLength+2:]
    94  
    95  	return randomString, flags, hash, nil
    96  }
    97  
    98  // SaltFromEmail generates a salt-value from an email address.
    99  func SaltFromEmail(email string) string {
   100  	return strings.ToLower(email)
   101  }
   102  
   103  func calculateHash(randomString string, flags int, salt string) hashpkg.Hash {
   104  	hash := md5.New()
   105  	_, _ = hash.Write([]byte(randomString))
   106  	_, _ = hash.Write([]byte(salt))
   107  	_, _ = hash.Write([]byte(fmt.Sprintf("%04x", flags)))
   108  	return hash
   109  }
   110  
   111  func generateRandomString(n int) (string, error) {
   112  	b := make([]byte, n)
   113  	_, err := rand.Read(b)
   114  	if err != nil {
   115  		return "", err
   116  	}
   117  
   118  	str := ""
   119  	for _, v := range b {
   120  		str += string(charset[int(v)%len(charset)])
   121  	}
   122  
   123  	return str, nil
   124  }