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 }