DockerCLI/vendor/github.com/docker/licensing/license.go

159 lines
4.4 KiB
Go
Raw Normal View History

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
}