175 lines
4.5 KiB
Go
175 lines
4.5 KiB
Go
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
|
|
}
|