package registry import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/json" "fmt" "github.com/docker/distribution/storage" ) // tokenProvider contains methods for serializing and deserializing state from token strings. type tokenProvider interface { // LayerUploadStateFromToken retrieves the LayerUploadState for a given state token. LayerUploadStateFromToken(stateToken string) (storage.LayerUploadState, error) // LayerUploadStateToToken returns a token string representing the given LayerUploadState. LayerUploadStateToToken(layerUploadState storage.LayerUploadState) (string, error) } type hmacTokenProvider struct { secret string } func newHMACTokenProvider(secret string) tokenProvider { return &hmacTokenProvider{secret: secret} } // LayerUploadStateFromToken deserializes the given HMAC stateToken and validates the prefix HMAC func (ts *hmacTokenProvider) LayerUploadStateFromToken(stateToken string) (storage.LayerUploadState, error) { var lus storage.LayerUploadState tokenBytes, err := base64.URLEncoding.DecodeString(stateToken) if err != nil { return lus, err } mac := hmac.New(sha256.New, []byte(ts.secret)) if len(tokenBytes) < mac.Size() { return lus, fmt.Errorf("Invalid token") } macBytes := tokenBytes[:mac.Size()] messageBytes := tokenBytes[mac.Size():] mac.Write(messageBytes) if !hmac.Equal(mac.Sum(nil), macBytes) { return lus, fmt.Errorf("Invalid token") } if err := json.Unmarshal(messageBytes, &lus); err != nil { return lus, err } return lus, nil } // LayerUploadStateToToken serializes the given LayerUploadState to JSON with an HMAC prepended func (ts *hmacTokenProvider) LayerUploadStateToToken(lus storage.LayerUploadState) (string, error) { mac := hmac.New(sha256.New, []byte(ts.secret)) stateJSON := fmt.Sprintf("{\"Name\": \"%s\", \"UUID\": \"%s\", \"Offset\": %d}", lus.Name, lus.UUID, lus.Offset) mac.Write([]byte(stateJSON)) return base64.URLEncoding.EncodeToString(append(mac.Sum(nil), stateJSON...)), nil }