mirror of https://github.com/docker/cli.git
159 lines
4.4 KiB
Go
159 lines
4.4 KiB
Go
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
|
|
}
|