X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/0bae20041e6dd78b086ba1354b8de9aee4e6baed..c9b8b9b9c78a77dd30b828914c8bee9fa8dcbb90:/sdk/go/keepclient/perms.go diff --git a/sdk/go/keepclient/perms.go b/sdk/go/keepclient/perms.go index 5b792a8349..23ca7d2f2b 100644 --- a/sdk/go/keepclient/perms.go +++ b/sdk/go/keepclient/perms.go @@ -1,132 +1,16 @@ -/* -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 use 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 GET -that object. - -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. -*/ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 package keepclient -import ( - "crypto/hmac" - "crypto/sha1" - "fmt" - "regexp" - "strconv" - "strings" - "time" -) - -// KeepError types. -// -type KeepError struct { - HTTPCode int - ErrMsg string -} +import "git.arvados.org/arvados.git/sdk/go/arvados" var ( - PermissionError = &KeepError{403, "Forbidden"} - ExpiredError = &KeepError{401, "Expired permission signature"} + ErrSignatureExpired = arvados.ErrSignatureExpired + ErrSignatureInvalid = arvados.ErrSignatureInvalid + ErrSignatureMissing = arvados.ErrSignatureMissing + SignLocator = arvados.SignLocator + SignedLocatorRe = arvados.SignedLocatorRe + VerifySignature = arvados.VerifySignature ) - -func (e *KeepError) Error() string { - return e.ErrMsg -} - -// makePermSignature returns a string representing the signed permission -// hint for the blob identified by blobHash, apiToken, expiration timestamp, and permission secret. -// -// The permissionSecret is the secret key used to generate SHA1 digests -// for permission hints. apiserver and Keep must use the same key. -func makePermSignature(blobHash string, apiToken string, expiry string, permissionSecret []byte) string { - hmac := hmac.New(sha1.New, permissionSecret) - hmac.Write([]byte(blobHash)) - hmac.Write([]byte("@")) - hmac.Write([]byte(apiToken)) - hmac.Write([]byte("@")) - hmac.Write([]byte(expiry)) - digest := hmac.Sum(nil) - return fmt.Sprintf("%x", digest) -} - -// SignLocator takes a blobLocator, an apiToken, an expiry time, and a permission secret -// and returns a signed locator string. -func SignLocator(blobLocator string, apiToken string, expiry time.Time, permissionSecret []byte) string { - // If no permission secret or API token is available, - // return an unsigned locator. - if permissionSecret == nil || apiToken == "" { - return blobLocator - } - // Extract the hash from the blob locator, omitting any size hint that may be present. - blobHash := strings.Split(blobLocator, "+")[0] - // Return the signed locator string. - timestampHex := fmt.Sprintf("%08x", expiry.Unix()) - return blobLocator + - "+A" + makePermSignature(blobHash, apiToken, timestampHex, permissionSecret) + - "@" + timestampHex -} - -var signedLocatorRe = regexp.MustCompile(`^([[:xdigit:]]{32}).*\+A([[:xdigit:]]{40})@([[:xdigit:]]{8})`) - -// VerifySignature returns nil if the signature on the signedLocator -// can be verified using the given apiToken. Otherwise it returns -// either ExpiredError (if the timestamp has expired, which is -// something the client could have figured out independently) or -// PermissionError. -func VerifySignature(signedLocator string, apiToken string, permissionSecret []byte) error { - matches := signedLocatorRe.FindStringSubmatch(signedLocator) - if matches == nil { - // Could not find a permission signature at all - return PermissionError - } - blobHash := matches[1] - sigHex := matches[2] - expHex := matches[3] - if expTime, err := parseHexTimestamp(expHex); err != nil { - return PermissionError - } else if expTime.Before(time.Now()) { - return ExpiredError - } - if sigHex != makePermSignature(blobHash, apiToken, expHex, permissionSecret) { - return PermissionError - } - return nil -} - -// parseHexTimestamp parses timestamp -func parseHexTimestamp(timestampHex string) (ts time.Time, err error) { - if tsInt, e := strconv.ParseInt(timestampHex, 16, 0); e == nil { - ts = time.Unix(tsInt, 0) - } else { - err = e - } - return ts, err -}