多租户更改
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# keygen: API key generator
|
||||
|
||||
A command-line utility to generate an API key for [Tinode server](../server/)
|
||||
|
||||
**Parameters:**
|
||||
|
||||
* `sequence`: Sequential number of the API key. This value can be used to reject previously issued keys.
|
||||
* `isroot`: Currently unused. Intended to designate key of a system administrator.
|
||||
* `validate`: Key to validate: check previously issued key for validity.
|
||||
* `salt`: [HMAC](https://en.wikipedia.org/wiki/HMAC) salt, 32 random bytes base64 standard encoded; must be present for key validation; optional when generating the key: if missing, a cryptographically-strong salt will be automatically generated.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
The API key is used to provide some protection from automatic scraping of server API and for identification of client applications.
|
||||
|
||||
* `API key` is used on the client side.
|
||||
* `HMAC salt` is used on the server side to verify the API key.
|
||||
|
||||
Run the generator:
|
||||
|
||||
```sh
|
||||
./keygen
|
||||
```
|
||||
|
||||
Sample output:
|
||||
|
||||
```text
|
||||
API key v1 seq1 [ordinary]: AQAAAAABAACGOIyP2vh5avSff5oVvMpk
|
||||
HMAC salt: TC0Jzr8f28kAspXrb4UYccJUJ63b7CSA16n1qMxxGpw=
|
||||
```
|
||||
|
||||
Copy `HMAC salt` to `api_key_salt` parameter in your server [config file](https://github.com/tinode/chat/blob/master/server/tinode.conf).
|
||||
Copy `API key` to the client applications:
|
||||
|
||||
* TinodeWeb: `API_KEY` in [config.js](https://github.com/tinode/webapp/blob/master/src/config.js)
|
||||
* Tindroid: `API_KEY` in [Cache.java](https://github.com/tinode/tindroid/blob/master/app/src/main/java/co/tinode/tindroid/Cache.java)
|
||||
* Tinodious: `kApiKey` in [SharedUtils.swift](https://github.com/tinode/ios/blob/master/TinodiosDB/SharedUtils.swift)
|
||||
|
||||
Rebuild the clients after changing the API key.
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Generate API key
|
||||
// Composition:
|
||||
//
|
||||
// [1:algorithm version][4:deprecated (used to be application ID)][2:key sequence][1:isRoot][16:signature] = 24 bytes
|
||||
//
|
||||
// convertible to base64 without padding.
|
||||
// All integers are little-endian.
|
||||
func main() {
|
||||
version := flag.Int("sequence", 1, "Sequential number of the API key")
|
||||
isRoot := flag.Int("isroot", 0, "Is this a root API key?")
|
||||
apikey := flag.String("validate", "", "API key to validate")
|
||||
hmacSalt := flag.String("salt", "", "HMAC salt, 32 random bytes base64-encoded")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *apikey != "" {
|
||||
if *hmacSalt == "" {
|
||||
log.Println("Error: must provide HMAC salt for key validation")
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(validate(*apikey, *hmacSalt))
|
||||
} else {
|
||||
os.Exit(generate(*version, *isRoot, *hmacSalt))
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// APIKEY_VERSION is algorithm version.
|
||||
APIKEY_VERSION = 1
|
||||
// APIKEY_APPID is deprecated.
|
||||
APIKEY_APPID = 4
|
||||
// APIKEY_SEQUENCE key serial number.
|
||||
APIKEY_SEQUENCE = 2
|
||||
// APIKEY_WHO is a Root user designator.
|
||||
APIKEY_WHO = 1
|
||||
// APIKEY_SIGNATURE is cryptographic signature.
|
||||
APIKEY_SIGNATURE = 16
|
||||
// APIKEY_LENGTH is total length of the key.
|
||||
APIKEY_LENGTH = APIKEY_VERSION + APIKEY_APPID + APIKEY_SEQUENCE + APIKEY_WHO + APIKEY_SIGNATURE
|
||||
)
|
||||
|
||||
func generate(sequence, isRoot int, hmacSaltB64 string) int {
|
||||
var data [APIKEY_LENGTH]byte
|
||||
var hmacSalt []byte
|
||||
|
||||
if hmacSaltB64 == "" {
|
||||
hmacSalt = make([]byte, 32)
|
||||
_, err := rand.Read(hmacSalt)
|
||||
if err != nil {
|
||||
log.Println("Error: Failed to generate HMAC salt", err)
|
||||
|
||||
return 1
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
hmacSalt, err = base64.URLEncoding.DecodeString(hmacSaltB64)
|
||||
if err != nil {
|
||||
// Try standard base64 decoding
|
||||
hmacSalt, err = base64.StdEncoding.DecodeString(hmacSaltB64)
|
||||
}
|
||||
if err != nil {
|
||||
log.Println("Error: Failed to decode HMAC salt", err)
|
||||
|
||||
return 1
|
||||
}
|
||||
}
|
||||
// Make sure the salt is base64std encoded: tinode.conf requires std encoding.
|
||||
hmacSaltB64 = base64.StdEncoding.EncodeToString(hmacSalt)
|
||||
|
||||
// [1:algorithm version][4:appid][2:key sequence][1:isRoot]
|
||||
data[0] = 1 // default algorithm
|
||||
// deprecated
|
||||
binary.LittleEndian.PutUint32(data[APIKEY_VERSION:], uint32(0))
|
||||
binary.LittleEndian.PutUint16(data[APIKEY_VERSION+APIKEY_APPID:], uint16(sequence))
|
||||
data[APIKEY_VERSION+APIKEY_APPID+APIKEY_SEQUENCE] = uint8(isRoot)
|
||||
|
||||
hasher := hmac.New(md5.New, hmacSalt)
|
||||
hasher.Write(data[:APIKEY_VERSION+APIKEY_APPID+APIKEY_SEQUENCE+APIKEY_WHO])
|
||||
signature := hasher.Sum(nil)
|
||||
|
||||
copy(data[APIKEY_VERSION+APIKEY_APPID+APIKEY_SEQUENCE+APIKEY_WHO:], signature)
|
||||
|
||||
var strIsRoot string
|
||||
if isRoot == 1 {
|
||||
strIsRoot = "ROOT"
|
||||
} else {
|
||||
strIsRoot = "ordinary"
|
||||
}
|
||||
|
||||
fmt.Printf("API key v%d seq%d [%s]: %s\nHMAC salt: %s\n", 1, sequence, strIsRoot,
|
||||
base64.URLEncoding.EncodeToString(data[:]), hmacSaltB64)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func validate(apikey string, hmacSaltB64 string) int {
|
||||
var version uint8
|
||||
var deprecated uint32
|
||||
var sequence uint16
|
||||
var isRoot uint8
|
||||
|
||||
var strIsRoot string
|
||||
|
||||
hmacSalt, err := base64.URLEncoding.DecodeString(hmacSaltB64)
|
||||
if err != nil {
|
||||
// Try standard base64 decoding.
|
||||
hmacSalt, err = base64.StdEncoding.DecodeString(hmacSaltB64)
|
||||
}
|
||||
if err != nil {
|
||||
log.Println("Error: Failed to decode HMAC salt", err)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
if declen := base64.URLEncoding.DecodedLen(len(apikey)); declen != APIKEY_LENGTH {
|
||||
log.Printf("Error: Invalid key length %d, expecting %d", declen, APIKEY_LENGTH)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
data, err := base64.URLEncoding.DecodeString(apikey)
|
||||
if err != nil {
|
||||
log.Println("Error: Failed to decode key as base64-URL-encoded", err)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
buf := bytes.NewReader(data)
|
||||
binary.Read(buf, binary.LittleEndian, &version)
|
||||
|
||||
if version != 1 {
|
||||
log.Println("Error: Unknown signature algorithm ", version)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
hasher := hmac.New(md5.New, hmacSalt)
|
||||
hasher.Write(data[:APIKEY_VERSION+APIKEY_APPID+APIKEY_SEQUENCE+APIKEY_WHO])
|
||||
|
||||
if signature := hasher.Sum(nil); !bytes.Equal(data[APIKEY_VERSION+APIKEY_APPID+APIKEY_SEQUENCE+APIKEY_WHO:], signature) {
|
||||
log.Println("Error: Invalid signature ", data, signature)
|
||||
|
||||
return 1
|
||||
}
|
||||
// [1:algorithm version][4:deprecated][2:key sequence][1:isRoot]
|
||||
binary.Read(buf, binary.LittleEndian, &deprecated)
|
||||
binary.Read(buf, binary.LittleEndian, &sequence)
|
||||
binary.Read(buf, binary.LittleEndian, &isRoot)
|
||||
|
||||
if isRoot == 1 {
|
||||
strIsRoot = "ROOT"
|
||||
} else {
|
||||
strIsRoot = "ordinary"
|
||||
}
|
||||
|
||||
fmt.Printf("Valid v%d seq%d, [%s]\n", version, sequence, strIsRoot)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user