From 8023ae393e2c9ce115bf67bbcf632798a3d17b87 Mon Sep 17 00:00:00 2001 From: Tim Pierce Date: Fri, 2 May 2014 15:05:07 -0400 Subject: [PATCH] Added permission helper functions. GeneratePerms returns a string representing the signed permission hint for the blob identified by blob_hash, api_token and timestamp. SignLocator takes a blob_locator, an api_token and a timestamp, and returns a signed locator string. VerifySignature returns true if the signature on the signed_locator can be verified using the given api_token. Refs #2328. --- services/keep/src/keep/perms.go | 79 ++++++++++++++++++++++ services/keep/src/keep/perms_test.go | 97 ++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 services/keep/src/keep/perms.go create mode 100644 services/keep/src/keep/perms_test.go diff --git a/services/keep/src/keep/perms.go b/services/keep/src/keep/perms.go new file mode 100644 index 0000000000..7bbae4f7a1 --- /dev/null +++ b/services/keep/src/keep/perms.go @@ -0,0 +1,79 @@ +/* +Permissions management on Arvados locator hashes. + +The permissions structure for Arvados is as follows (from +https://arvados.org/issues/2328) + +A Keep locator string has the following format: + + [hash]+[size]+A[signature]@[timestamp] + +The "signature" string here is a cryptographic hash, expressed as a +string of hexadecimal digits, and timestamp is a 32-bit Unix timestamp +expressed as a hexadecimal number. e.g.: + + acbd18db4cc2f85cedef654fccc4a4d8+3+A257f3f5f5f0a4e4626a18fc74bd42ec34dcb228a@7fffffff + +The signature represents a guarantee that this locator was generated +by either Keep or the API server for the user with the supplied API +token. If a request to Keep includes a locator with a valid signature +and is accompanied by the proper API token, the user has permission to +perform any action on that object (GET, PUT or DELETE). + +The signature may be generated either by Keep (after the user writes a +block) or by the API server (if the user has can_read permission on +the specified object). Keep and API server share a secret that is used +to generate signatures. + +To verify a permission hint, Keep generates a new hint for the +requested object (using the locator string, the timestamp, the +permission secret and the user's API token, which must appear in the +request headers) and compares it against the hint included in the +request. If the permissions do not match, or if the API token is not +present, Keep returns a 401 error. +*/ + +package main + +import ( + "crypto/hmac" + "crypto/sha1" + "fmt" + "regexp" + "strings" +) + +// The PermissionSecret is the secret key used to generate SHA1 digests +// for permission hints. apiserver and Keep must use the same key. +var PermissionSecret []byte + +// GeneratePerms returns a string representing the permission hint for a blob +// with the given hash, API token and timestamp. +func GeneratePerms(blob_hash string, api_token string, timestamp string) string { + hmac := hmac.New(sha1.New, PermissionSecret) + hmac.Write([]byte(blob_hash)) + hmac.Write([]byte("@")) + hmac.Write([]byte(api_token)) + hmac.Write([]byte("@")) + hmac.Write([]byte(timestamp)) + digest := hmac.Sum(nil) + return fmt.Sprintf("%x", digest) +} + +func SignLocator(blob_locator string, api_token string, timestamp string) string { + // Extract the hash from the blob locator, omitting any size hint that may be present. + blob_hash := strings.Split(blob_locator, "+")[0] + // Return the signed locator string. + return blob_locator + "+A" + GeneratePerms(blob_hash, api_token, timestamp) + "@" + timestamp +} + +func VerifySignature(signed_locator string, api_token string) bool { + if re, err := regexp.Compile(`^(.*)\+A(.*)@(.*)$`); err == nil { + if matches := re.FindStringSubmatch(signed_locator); matches != nil { + blob_locator := matches[1] + timestamp := matches[3] + return signed_locator == SignLocator(blob_locator, api_token, timestamp) + } + } + return false +} diff --git a/services/keep/src/keep/perms_test.go b/services/keep/src/keep/perms_test.go new file mode 100644 index 0000000000..fcd17e1cd2 --- /dev/null +++ b/services/keep/src/keep/perms_test.go @@ -0,0 +1,97 @@ +package main + +import ( + "testing" +) + +var ( + known_hash = "acbd18db4cc2f85cedef654fccc4a4d8" + known_locator = known_hash + "+3" + known_token = "hocfupkn2pjhrpgp2vxv8rsku7tvtx49arbc9s4bvu7p7wxqvk" + known_key = "13u9fkuccnboeewr0ne3mvapk28epf68a3bhj9q8sb4l6e4e5mkk" + + "p6nhj2mmpscgu1zze5h5enydxfe3j215024u16ij4hjaiqs5u4pzsl3nczmaoxnc" + + "ljkm4875xqn4xv058koz3vkptmzhyheiy6wzevzjmdvxhvcqsvr5abhl15c2d4o4" + + "jhl0s91lojy1mtrzqqvprqcverls0xvy9vai9t1l1lvvazpuadafm71jl4mrwq2y" + + "gokee3eamvjy8qq1fvy238838enjmy5wzy2md7yvsitp5vztft6j4q866efym7e6" + + "vu5wm9fpnwjyxfldw3vbo01mgjs75rgo7qioh8z8ij7jpyp8508okhgbbex3ceei" + + "786u5rw2a9gx743dj3fgq2irk" + known_signature = "257f3f5f5f0a4e4626a18fc74bd42ec34dcb228a" + known_timestamp = "7fffffff" + known_signed_locator = known_locator + "+A" + known_signature + "@" + known_timestamp +) + +func TestGeneratePerms(t *testing.T) { + PermissionSecret = []byte(known_key) + defer func() { PermissionSecret = nil }() + + if known_signature != GeneratePerms(known_hash, known_token, known_timestamp) { + t.Fail() + } +} + +func TestSignLocator(t *testing.T) { + PermissionSecret = []byte(known_key) + defer func() { PermissionSecret = nil }() + + if known_signed_locator != SignLocator(known_locator, known_token, known_timestamp) { + t.Fail() + } +} + +func TestVerifySignature(t *testing.T) { + PermissionSecret = []byte(known_key) + defer func() { PermissionSecret = nil }() + + if !VerifySignature(known_signed_locator, known_token) { + t.Fail() + } +} + +// The size hint on the locator string should not affect signature validation. +func TestVerifySignatureWrongSize(t *testing.T) { + PermissionSecret = []byte(known_key) + defer func() { PermissionSecret = nil }() + + signed_locator_wrong_size := known_hash + "+999999+A" + known_signature + "@" + known_timestamp + if !VerifySignature(signed_locator_wrong_size, known_token) { + t.Fail() + } +} + +func TestVerifySignatureBadSig(t *testing.T) { + PermissionSecret = []byte(known_key) + defer func() { PermissionSecret = nil }() + + bad_locator := known_locator + "+Aaaaaaaaaaaaaaaa@" + known_timestamp + if VerifySignature(bad_locator, known_token) { + t.Fail() + } +} + +func TestVerifySignatureBadTimestamp(t *testing.T) { + PermissionSecret = []byte(known_key) + defer func() { PermissionSecret = nil }() + + bad_locator := known_locator + "+A" + known_signature + "@00000000" + if VerifySignature(bad_locator, known_token) { + t.Fail() + } +} + +func TestVerifySignatureBadSecret(t *testing.T) { + PermissionSecret = []byte("00000000000000000000") + defer func() { PermissionSecret = nil }() + + if VerifySignature(known_signed_locator, known_token) { + t.Fail() + } +} + +func TestVerifySignatureBadToken(t *testing.T) { + PermissionSecret = []byte(known_key) + defer func() { PermissionSecret = nil }() + + if VerifySignature(known_signed_locator, "00000000") { + t.Fail() + } +} -- 2.30.2