package licensing import ( "context" "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/json" "fmt" "time" "github.com/docker/libtrust" "github.com/docker/licensing/lib/errors" "github.com/docker/licensing/lib/go-clientlib" "github.com/docker/licensing/model" ) func (c *client) getLicenseFile(ctx context.Context, subID string) (*model.IssuedLicense, error) { url := c.baseURI url.Path += fmt.Sprintf("/api/billing/v4/subscriptions/%s/license-file", subID) license := new(model.IssuedLicense) if _, _, err := c.doReq(ctx, "GET", &url, clientlib.RecvJSON(license)); err != nil { return nil, err } return license, nil } // Check verifies that the license identified by the given key id is valid. Note that it does not // interrogate the contents of the license. func (c *client) check(ctx context.Context, license model.IssuedLicense) (*model.CheckResponse, error) { keyID := license.KeyID privateKey := license.PrivateKey authorization, err := c.getAuthorization(ctx, license) if err != nil { return nil, err } // TODO: Mason - replace this parseJWS with a non libtrust lib signature, err := libtrust.ParseJWS(authorization) if err != nil { return nil, errors.Wrapf(err, errors.Fields{ "key_id": keyID, }, "license parse JWS failed") } keys, err := signature.Verify() if err != nil { return nil, errors.Wrapf(err, errors.Fields{ "key_id": keyID, }, "license signature verification failed") } keyCnt := len(keys) if keyCnt != 1 { err = fmt.Errorf("unexpected number of signing keys (%d)", keyCnt) return nil, errors.WithStack(err).With(errors.Fields{ "key_id": keyID, }) } key := keys[0] if !c.recognizedSigningKey(key) { return nil, errors.New("unrecognized signing key") } payload, err := signature.Payload() if err != nil { return nil, errors.Wrapf(err, errors.Fields{ "key_id": keyID, }, "malformed signature payload") } checkRes := new(model.CheckResponse) err = json.Unmarshal(payload, &checkRes) if err != nil { return nil, errors.Wrapf(err, errors.Fields{ "key_id": keyID, }, "license payload unmarshal failed") } msg := checkRes.Expiration.Format(time.RFC3339) if err := checkToken(msg, checkRes.Token, privateKey); err != nil { return nil, errors.Wrap(err, errors.Fields{ "key_id": keyID, }) } return checkRes, nil } // recognizedSigningKey returns true if the given key is signed with a recognized signing key, false otherwise func (c *client) recognizedSigningKey(key libtrust.PublicKey) bool { for _, publicKey := range c.publicKeys { if key.KeyID() == publicKey.KeyID() { return true } } return false } // getAuthorization returns the decoded license authorization func (c *client) getAuthorization(ctx context.Context, license model.IssuedLicense) ([]byte, error) { decoded, err := base64.StdEncoding.DecodeString(license.Authorization) if err != nil { return nil, errors.Wrapf(err, errors.Fields{ "key_id": license.KeyID, }, "decoding license authorization failed") } return decoded, nil } // All of the functions in this file assume that they are receiving a properly // formatted private key. // checkToken performs a MAC algorithm (where token is generated by hashing the // message with the privateKey via GenerateToken) with the purpose of authenticating // the validity of both the message and the private key of the person who generated // the token. func checkToken(message, token, privateKey string) error { tokenBytes, err := base64.URLEncoding.DecodeString(token) if err != nil { return errors.Wrap(err, errors.Fields{"token": token}) } generatedToken, err := generateToken(message, privateKey) if err != nil { return errors.Wrap(err, errors.Fields{"token": token}) } generatedBytes, err := base64.URLEncoding.DecodeString(generatedToken) if err != nil { return errors.Wrap(err, errors.Fields{"token": token}) } if !hmac.Equal(tokenBytes, generatedBytes) { return errors.Forbidden(errors.Fields{"token": token}, "invalid token") } return nil } // generateToken generates a hash of the message with the privateKey via the // sha256 algorithm. func generateToken(message, privateKey string) (string, error) { key, err := base64.URLEncoding.DecodeString(privateKey) if err != nil { return "", errors.Wrap(err, errors.Fields{"msg": message}) } h := hmac.New(sha256.New, key) h.Write([]byte(message)) return base64.URLEncoding.EncodeToString(h.Sum(nil)), nil }