mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-28 04:10:44 -04:00
Update to new gcp-auth plugin
This commit is contained in:
parent
3c143bd0d3
commit
da1eb4cf16
44 changed files with 102552 additions and 325 deletions
51
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/backend.go
generated
vendored
51
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/backend.go
generated
vendored
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/hashicorp/vault/version"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
"google.golang.org/api/compute/v1"
|
||||
"google.golang.org/api/iam/v1"
|
||||
)
|
||||
|
||||
|
|
@ -26,8 +27,9 @@ type GcpAuthBackend struct {
|
|||
// Locks for guarding service clients
|
||||
clientMutex sync.RWMutex
|
||||
|
||||
// GCP service clients
|
||||
// -- GCP service clients --
|
||||
iamClient *iam.Service
|
||||
gceClient *compute.Service
|
||||
}
|
||||
|
||||
// Factory returns a new backend as logical.Backend.
|
||||
|
|
@ -43,6 +45,7 @@ func Backend() *GcpAuthBackend {
|
|||
b := &GcpAuthBackend{
|
||||
oauthScopes: []string{
|
||||
iam.CloudPlatformScope,
|
||||
compute.ComputeReadonlyScope,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -80,6 +83,7 @@ func (b *GcpAuthBackend) Close() {
|
|||
defer b.clientMutex.Unlock()
|
||||
|
||||
b.iamClient = nil
|
||||
b.gceClient = nil
|
||||
}
|
||||
|
||||
func (b *GcpAuthBackend) IAM(s logical.Storage) (*iam.Service, error) {
|
||||
|
|
@ -104,6 +108,28 @@ func (b *GcpAuthBackend) IAM(s logical.Storage) (*iam.Service, error) {
|
|||
return b.iamClient, nil
|
||||
}
|
||||
|
||||
func (b *GcpAuthBackend) GCE(s logical.Storage) (*compute.Service, error) {
|
||||
b.clientMutex.RLock()
|
||||
if b.gceClient != nil {
|
||||
defer b.clientMutex.RUnlock()
|
||||
return b.gceClient, nil
|
||||
}
|
||||
|
||||
b.clientMutex.RUnlock()
|
||||
b.clientMutex.Lock()
|
||||
defer b.clientMutex.Unlock()
|
||||
|
||||
// Check if client was created during lock switch.
|
||||
if b.gceClient == nil {
|
||||
err := b.initClients(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return b.gceClient, nil
|
||||
}
|
||||
|
||||
// Initialize attempts to create GCP clients from stored config.
|
||||
// It does not attempt to claim the client lock.
|
||||
func (b *GcpAuthBackend) initClients(s logical.Storage) (err error) {
|
||||
|
|
@ -137,16 +163,27 @@ func (b *GcpAuthBackend) initClients(s logical.Storage) (err error) {
|
|||
}
|
||||
b.iamClient.UserAgent = userAgentStr
|
||||
|
||||
b.gceClient, err = compute.New(httpClient)
|
||||
if err != nil {
|
||||
b.Close()
|
||||
return err
|
||||
}
|
||||
b.gceClient.UserAgent = userAgentStr
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const backendHelp = `
|
||||
The GCP credential provider allows authentication for Google Cloud Platform entities.
|
||||
Currently supports authentication for:
|
||||
The GCP backend plugin allows authentication for Google Cloud Platform entities.
|
||||
Currently, it supports authentication for:
|
||||
|
||||
IAM service accounts:
|
||||
* IAM Service Accounts:
|
||||
IAM service accounts provide a signed JSON Web Token (JWT), signed by
|
||||
calling GCP APIs directly or via the Vault CL helper. If successful,
|
||||
Vault will also return a client nonce that is required as the 'jti'
|
||||
field for all subsequent logins by this instance.
|
||||
calling GCP APIs directly or via the Vault CL helper.
|
||||
|
||||
* GCE VM Instances:
|
||||
GCE provide a signed instance metadata JSON Web Token (JWT), obtained from the
|
||||
GCE instance metadata server (http://metadata.google.internal/computeMetadata/v1/instance).
|
||||
Using the /service-accounts/<service-account-name>/identity endpoint, the instance
|
||||
can obtain this JWT and pass it to Vault on login.
|
||||
`
|
||||
|
|
|
|||
27
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/path_config.go
generated
vendored
27
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/path_config.go
generated
vendored
|
|
@ -21,6 +21,12 @@ func pathConfig(b *GcpAuthBackend) *framework.Path {
|
|||
Google credentials JSON that Vault will use to verify users against GCP APIs.
|
||||
If not specified, will use application default credentials`,
|
||||
},
|
||||
"google_certs_endpoint": {
|
||||
Type: framework.TypeString,
|
||||
Description: `
|
||||
Base endpoint url that Vault will use to get Google certificates.
|
||||
If not specified, will use the OAuth2 library default. Useful for testing.`,
|
||||
},
|
||||
},
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ReadOperation: b.pathConfigRead,
|
||||
|
|
@ -68,11 +74,12 @@ func (b *GcpAuthBackend) pathConfigRead(req *logical.Request, data *framework.Fi
|
|||
|
||||
resp := &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"client_email": config.Credentials.ClientEmail,
|
||||
"client_id": config.Credentials.ClientId,
|
||||
"private_key_id": config.Credentials.PrivateKeyId,
|
||||
"private_key": config.Credentials.PrivateKey,
|
||||
"project_id": config.Credentials.ProjectId,
|
||||
"client_email": config.Credentials.ClientEmail,
|
||||
"client_id": config.Credentials.ClientId,
|
||||
"private_key_id": config.Credentials.PrivateKeyId,
|
||||
"private_key": config.Credentials.PrivateKey,
|
||||
"project_id": config.Credentials.ProjectId,
|
||||
"google_certs_endpoint": config.GoogleCertsEndpoint,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -91,10 +98,9 @@ iam AUTH:
|
|||
`
|
||||
|
||||
// gcpConfig contains all config required for the GCP backend.
|
||||
// Currently it only holds credentials, but we are leaving it as a seperate
|
||||
// struct in case we add more fields in the future.
|
||||
type gcpConfig struct {
|
||||
Credentials *util.GcpCredentials `json:"credentials" structs:"credentials" mapstructure:"credentials"`
|
||||
Credentials *util.GcpCredentials `json:"credentials" structs:"credentials" mapstructure:"credentials"`
|
||||
GoogleCertsEndpoint string `json:"google_certs_endpoint" structs:"google_certs_endpoint" mapstructure:"google_certs_endpoint"`
|
||||
}
|
||||
|
||||
// Update sets gcpConfig values parsed from the FieldData.
|
||||
|
|
@ -111,6 +117,11 @@ func (config *gcpConfig) Update(data *framework.FieldData) error {
|
|||
config.Credentials = creds
|
||||
}
|
||||
|
||||
certsEndpoint := data.Get("google_certs_endpoint").(string)
|
||||
if len(certsEndpoint) > 0 {
|
||||
config.GoogleCertsEndpoint = certsEndpoint
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
387
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/path_login.go
generated
vendored
387
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/path_login.go
generated
vendored
|
|
@ -3,25 +3,22 @@ package gcpauth
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/SermoDigital/jose/crypto"
|
||||
"github.com/SermoDigital/jose/jws"
|
||||
"github.com/SermoDigital/jose/jwt"
|
||||
"github.com/hashicorp/vault-plugin-auth-gcp/plugin/util"
|
||||
"github.com/hashicorp/vault/helper/policyutil"
|
||||
"github.com/hashicorp/vault/helper/strutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
"google.golang.org/api/compute/v1"
|
||||
"google.golang.org/api/iam/v1"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
expectedJwtAudTemplate string = "vault/%s"
|
||||
|
||||
// Default duration that JWT tokens must expire within to be accepted
|
||||
defaultMaxJwtExpMin int = 15
|
||||
|
||||
clientErrorTemplate string = "backend not configured properly, could not create %s client: %v"
|
||||
)
|
||||
|
||||
|
|
@ -34,8 +31,10 @@ func pathLogin(b *GcpAuthBackend) *framework.Path {
|
|||
Description: `Name of the role against which the login is being attempted. Required.`,
|
||||
},
|
||||
"jwt": {
|
||||
Type: framework.TypeString,
|
||||
Description: `A signed JWT for authenticating a service account.`,
|
||||
Type: framework.TypeString,
|
||||
Description: `
|
||||
A signed JWT. This is either a self-signed service account JWT ('iam' roles only) or a
|
||||
GCE identity metadata token ('iam', 'gce' roles).`,
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -50,7 +49,7 @@ func pathLogin(b *GcpAuthBackend) *framework.Path {
|
|||
}
|
||||
|
||||
func (b *GcpAuthBackend) pathLogin(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
loginInfo, err := b.parseInfoFromJwt(req, data)
|
||||
loginInfo, err := b.parseAndValidateJwt(req, data)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
|
@ -59,6 +58,8 @@ func (b *GcpAuthBackend) pathLogin(req *logical.Request, data *framework.FieldDa
|
|||
switch roleType {
|
||||
case iamRoleType:
|
||||
return b.pathIamLogin(req, loginInfo)
|
||||
case gceRoleType:
|
||||
return b.pathGceLogin(req, loginInfo)
|
||||
default:
|
||||
return logical.ErrorResponse(fmt.Sprintf("login against role type '%s' is unsupported", roleType)), nil
|
||||
}
|
||||
|
|
@ -84,6 +85,10 @@ func (b *GcpAuthBackend) pathLoginRenew(req *logical.Request, data *framework.Fi
|
|||
if err := b.pathIamRenew(req, role); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
case gceRoleType:
|
||||
if err := b.pathGceRenew(req, role); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected role type '%s' for login renewal", role.RoleType)
|
||||
}
|
||||
|
|
@ -109,14 +114,14 @@ type gcpLoginInfo struct {
|
|||
// ID or email of an IAM service account or that inferred for a GCE VM.
|
||||
ServiceAccountId string
|
||||
|
||||
// ID of the public key to verify the signed JWT.
|
||||
KeyId string
|
||||
// Base JWT Claims (registered claims such as 'exp', 'iss', etc)
|
||||
JWTClaims *jwt.Claims
|
||||
|
||||
// Signed JWT
|
||||
JWT jwt.JWT
|
||||
// Metadata from a GCE instance identity token.
|
||||
GceMetadata *util.GCEIdentityMetadata
|
||||
}
|
||||
|
||||
func (b *GcpAuthBackend) parseInfoFromJwt(req *logical.Request, data *framework.FieldData) (*gcpLoginInfo, error) {
|
||||
func (b *GcpAuthBackend) parseAndValidateJwt(req *logical.Request, data *framework.FieldData) (*gcpLoginInfo, error) {
|
||||
loginInfo := &gcpLoginInfo{}
|
||||
var err error
|
||||
|
||||
|
|
@ -133,73 +138,122 @@ func (b *GcpAuthBackend) parseInfoFromJwt(req *logical.Request, data *framework.
|
|||
return nil, fmt.Errorf("role '%s' not found", loginInfo.RoleName)
|
||||
}
|
||||
|
||||
// Process JWT string.
|
||||
signedJwt, ok := data.GetOk("jwt")
|
||||
if !ok {
|
||||
return nil, errors.New("jwt argument is required")
|
||||
}
|
||||
signedJwtBytes := []byte(signedJwt.(string))
|
||||
|
||||
// Parse into JWS to get header values.
|
||||
jwsVal, err := jws.Parse(signedJwtBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headerVal := jwsVal.Protected()
|
||||
|
||||
if headerVal.Has("kid") {
|
||||
loginInfo.KeyId = jwsVal.Protected().Get("kid").(string)
|
||||
} else {
|
||||
return nil, errors.New("provided JWT must have 'kid' header value")
|
||||
}
|
||||
|
||||
// Parse claims
|
||||
loginInfo.JWT, err = jws.ParseJWT(signedJwtBytes)
|
||||
// Parse 'kid' key id from headers.
|
||||
jwtVal, err := jwt.ParseSigned(signedJwt.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sub, ok := loginInfo.JWT.Claims().Subject()
|
||||
if !ok {
|
||||
return nil, errors.New("expected JWT to have 'sub' claim with service account id or email")
|
||||
key, err := b.getSigningKey(jwtVal, signedJwt.(string), loginInfo.Role, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse claims and verify signature.
|
||||
baseClaims := &jwt.Claims{}
|
||||
customClaims := &util.CustomJWTClaims{}
|
||||
|
||||
if err = jwtVal.Claims(key, baseClaims, customClaims); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = validateBaseJWTClaims(baseClaims, loginInfo.RoleName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
loginInfo.JWTClaims = baseClaims
|
||||
|
||||
if len(baseClaims.Subject) == 0 {
|
||||
return nil, errors.New("expected JWT to have non-empty 'sub' claim")
|
||||
}
|
||||
loginInfo.ServiceAccountId = baseClaims.Subject
|
||||
|
||||
if customClaims.Google != nil && customClaims.Google.Compute != nil && len(customClaims.Google.Compute.InstanceId) > 0 {
|
||||
loginInfo.GceMetadata = customClaims.Google.Compute
|
||||
}
|
||||
|
||||
if loginInfo.Role.RoleType == gceRoleType && loginInfo.GceMetadata == nil {
|
||||
return nil, errors.New("expected JWT to have claims with GCE metadata")
|
||||
}
|
||||
loginInfo.ServiceAccountId = sub
|
||||
|
||||
return loginInfo, nil
|
||||
}
|
||||
|
||||
func (info *gcpLoginInfo) validateJWT(keyPEM string, loginInfo *gcpLoginInfo) error {
|
||||
pubKey, err := util.PublicKey(keyPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
func (b *GcpAuthBackend) getSigningKey(token *jwt.JSONWebToken, rawToken string, role *gcpRole, s logical.Storage) (interface{}, error) {
|
||||
if len(token.Headers) != 1 {
|
||||
return nil, errors.New("expected token to have exactly one header")
|
||||
}
|
||||
|
||||
validator := &jwt.Validator{
|
||||
Expected: jwt.Claims{
|
||||
"aud": fmt.Sprintf(expectedJwtAudTemplate, loginInfo.RoleName),
|
||||
},
|
||||
Fn: func(c jwt.Claims) error {
|
||||
exp, ok := c.Expiration()
|
||||
if !ok {
|
||||
return errors.New("JWT claim 'exp' is required")
|
||||
}
|
||||
if exp.After(time.Now().Add(loginInfo.Role.MaxJwtExp)) {
|
||||
return fmt.Errorf("JWT expires in %v minutes but must expire within %v for this role. Please generate a new token with a valid expiration.",
|
||||
int(exp.Sub(time.Now())/time.Minute), loginInfo.Role.MaxJwtExp)
|
||||
}
|
||||
keyId := token.Headers[0].KeyID
|
||||
|
||||
return nil
|
||||
},
|
||||
switch role.RoleType {
|
||||
case iamRoleType:
|
||||
iamClient, err := b.IAM(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serviceAccountId, err := util.ParseServiceAccountFromIAMJWT(rawToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accountKey, err := util.ServiceAccountKey(iamClient, keyId, serviceAccountId, role.ProjectId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return util.PublicKey(accountKey.PublicKeyData)
|
||||
case gceRoleType:
|
||||
var certsEndpoint string
|
||||
conf, err := b.config(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read config for backend: %v", err)
|
||||
}
|
||||
if conf != nil {
|
||||
certsEndpoint = conf.GoogleCertsEndpoint
|
||||
}
|
||||
|
||||
key, err := util.OAuth2RSAPublicKey(keyId, certsEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected role type %s", role.RoleType)
|
||||
}
|
||||
}
|
||||
|
||||
func validateBaseJWTClaims(c *jwt.Claims, roleName string) error {
|
||||
exp := c.Expiry.Time()
|
||||
if exp.IsZero() || exp.Before(time.Now()) {
|
||||
return errors.New("JWT is expired or does not have proper 'exp' claim")
|
||||
} else if exp.After(time.Now().Add(time.Minute * time.Duration(maxJwtExpMaxMinutes))) {
|
||||
return fmt.Errorf("JWT must expire in %d minutes", maxJwtExpMaxMinutes)
|
||||
}
|
||||
|
||||
if err := info.JWT.Validate(pubKey, crypto.SigningMethodRS256, validator); err != nil {
|
||||
return fmt.Errorf("invalid JWT: %v", err)
|
||||
sub := c.Subject
|
||||
if len(sub) < 0 {
|
||||
return errors.New("expected JWT to have 'sub' claim with service account id or email")
|
||||
}
|
||||
|
||||
expectedAudSuffix := fmt.Sprintf(expectedJwtAudTemplate, roleName)
|
||||
for _, aud := range c.Audience {
|
||||
if !strings.HasSuffix(aud, expectedAudSuffix) {
|
||||
return fmt.Errorf("at least one of the JWT claim 'aud' must end in '%s'", expectedAudSuffix)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---- IAM login domain ----
|
||||
|
||||
// pathIamLogin attempts a login operation using the parsed login info.
|
||||
func (b *GcpAuthBackend) pathIamLogin(req *logical.Request, loginInfo *gcpLoginInfo) (*logical.Response, error) {
|
||||
iamClient, err := b.IAM(req.Storage)
|
||||
if err != nil {
|
||||
|
|
@ -207,17 +261,17 @@ func (b *GcpAuthBackend) pathIamLogin(req *logical.Request, loginInfo *gcpLoginI
|
|||
}
|
||||
|
||||
role := loginInfo.Role
|
||||
|
||||
// Verify and get service account from signed JWT.
|
||||
key, err := util.ServiceAccountKey(iamClient, loginInfo.KeyId, loginInfo.ServiceAccountId, role.ProjectId)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("service account %s has no key with id %s", loginInfo.ServiceAccountId, loginInfo.KeyId)), nil
|
||||
if !role.AllowGCEInference && loginInfo.GceMetadata != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf(
|
||||
"IAM role '%s' does not allow gce inference but GCE instance metadata token given", loginInfo.RoleName)), nil
|
||||
}
|
||||
|
||||
if err := loginInfo.validateJWT(key.PublicKeyData, loginInfo); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
// TODO(emilymye): move to general JWT validation once custom expiry is supported for other JWT types.
|
||||
if loginInfo.JWTClaims.Expiry.Time().After(time.Now().Add(role.MaxJwtExp)) {
|
||||
return logical.ErrorResponse(fmt.Sprintf("role requires that JWTs must expire within %d seconds", int(role.MaxJwtExp/time.Second))), nil
|
||||
}
|
||||
|
||||
// Get service account and make sure it still exists.
|
||||
serviceAccount, err := util.ServiceAccount(iamClient, loginInfo.ServiceAccountId, role.ProjectId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -237,7 +291,7 @@ func (b *GcpAuthBackend) pathIamLogin(req *logical.Request, loginInfo *gcpLoginI
|
|||
}
|
||||
|
||||
// Validate service account can login against role.
|
||||
if err := b.validateAgainstIAMRole(serviceAccount, role); err != nil {
|
||||
if err := b.authorizeIAMServiceAccount(serviceAccount, role); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
||||
|
|
@ -264,6 +318,8 @@ func (b *GcpAuthBackend) pathIamLogin(req *logical.Request, loginInfo *gcpLoginI
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
// pathIamRenew returns an error if the service account referenced in the auth token metadata cannot renew the
|
||||
// auth token for the given role.
|
||||
func (b *GcpAuthBackend) pathIamRenew(req *logical.Request, role *gcpRole) error {
|
||||
iamClient, err := b.IAM(req.Storage)
|
||||
if err != nil {
|
||||
|
|
@ -280,7 +336,7 @@ func (b *GcpAuthBackend) pathIamRenew(req *logical.Request, role *gcpRole) error
|
|||
return fmt.Errorf("cannot find service account %s", serviceAccountId)
|
||||
}
|
||||
|
||||
if err := b.validateAgainstIAMRole(serviceAccount, role); err != nil {
|
||||
if err := b.authorizeIAMServiceAccount(serviceAccount, role); err != nil {
|
||||
return errors.New("service account is no longer authorized for role")
|
||||
}
|
||||
|
||||
|
|
@ -288,20 +344,20 @@ func (b *GcpAuthBackend) pathIamRenew(req *logical.Request, role *gcpRole) error
|
|||
}
|
||||
|
||||
// validateAgainstIAMRole returns an error if the given IAM service account is not authorized for the role.
|
||||
func (b *GcpAuthBackend) validateAgainstIAMRole(serviceAccount *iam.ServiceAccount, role *gcpRole) error {
|
||||
func (b *GcpAuthBackend) authorizeIAMServiceAccount(serviceAccount *iam.ServiceAccount, role *gcpRole) error {
|
||||
// This is just in case - project should already be used to retrieve service account.
|
||||
if role.ProjectId != serviceAccount.ProjectId {
|
||||
return fmt.Errorf("service account %s does not belong to project %s", serviceAccount.Email, role.ProjectId)
|
||||
}
|
||||
|
||||
// Check if role has the wildcard as the only service account.
|
||||
if len(role.ServiceAccounts) == 1 && role.ServiceAccounts[0] == serviceAccountWildcard {
|
||||
if len(role.BoundServiceAccounts) == 1 && role.BoundServiceAccounts[0] == serviceAccountsWildcard {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check for service account id/email.
|
||||
if strutil.StrListContains(role.ServiceAccounts, serviceAccount.Email) ||
|
||||
strutil.StrListContains(role.ServiceAccounts, serviceAccount.UniqueId) {
|
||||
if strutil.StrListContains(role.BoundServiceAccounts, serviceAccount.Email) ||
|
||||
strutil.StrListContains(role.BoundServiceAccounts, serviceAccount.UniqueId) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -309,6 +365,195 @@ func (b *GcpAuthBackend) validateAgainstIAMRole(serviceAccount *iam.ServiceAccou
|
|||
serviceAccount.Email, serviceAccount.UniqueId)
|
||||
}
|
||||
|
||||
// ---- GCE login domain ----
|
||||
// pathGceLogin attempts a login operation using the parsed login info.
|
||||
func (b *GcpAuthBackend) pathGceLogin(req *logical.Request, loginInfo *gcpLoginInfo) (*logical.Response, error) {
|
||||
role := loginInfo.Role
|
||||
metadata := loginInfo.GceMetadata
|
||||
fmt.Printf("here\n")
|
||||
fmt.Printf("metadata.CreatedAt \n")
|
||||
if metadata == nil {
|
||||
return logical.ErrorResponse("could not get GCE metadata from given JWT"), nil
|
||||
}
|
||||
|
||||
if role.ProjectId != metadata.ProjectId {
|
||||
return logical.ErrorResponse(fmt.Sprintf(
|
||||
"GCE instance must belong to project %s; metadata given has project %s",
|
||||
role.ProjectId, metadata.ProjectId)), nil
|
||||
}
|
||||
|
||||
// Verify instance exists.
|
||||
gceClient, err := b.GCE(req.Storage)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf(clientErrorTemplate, "GCE", err)), nil
|
||||
}
|
||||
|
||||
instance, err := metadata.GetVerifiedInstance(gceClient)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf(
|
||||
"error when attempting to find instance (project %s, zone: %s, instance: %s) :%v",
|
||||
metadata.ProjectId, metadata.Zone, metadata.InstanceName, err)), nil
|
||||
}
|
||||
|
||||
if err := b.authorizeGCEInstance(instance, req.Storage, role, metadata.Zone, loginInfo.ServiceAccountId); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
||||
resp := &logical.Response{
|
||||
Auth: &logical.Auth{
|
||||
InternalData: map[string]interface{}{},
|
||||
Period: role.Period,
|
||||
Persona: &logical.Persona{
|
||||
Name: fmt.Sprintf("gce-%s", strconv.FormatUint(instance.Id, 10)),
|
||||
},
|
||||
Policies: role.Policies,
|
||||
Metadata: map[string]string{
|
||||
"project_id": metadata.ProjectId,
|
||||
"project_number": strconv.FormatInt(metadata.ProjectNumber, 10),
|
||||
"zone": metadata.Zone,
|
||||
"instance_id": metadata.InstanceId,
|
||||
"instance_name": metadata.InstanceName,
|
||||
"instance_creation_timestamp": strconv.FormatInt(metadata.CreatedAt, 10),
|
||||
"service_account_id": loginInfo.ServiceAccountId,
|
||||
"role": loginInfo.RoleName,
|
||||
},
|
||||
DisplayName: instance.Name,
|
||||
LeaseOptions: logical.LeaseOptions{
|
||||
Renewable: true,
|
||||
TTL: role.TTL,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// pathGceRenew returns an error if the instance referenced in the auth token metadata cannot renew the
|
||||
// auth token for the given role.
|
||||
func (b *GcpAuthBackend) pathGceRenew(req *logical.Request, role *gcpRole) error {
|
||||
gceClient, err := b.GCE(req.Storage)
|
||||
if err != nil {
|
||||
return fmt.Errorf(clientErrorTemplate, "GCE", err)
|
||||
}
|
||||
|
||||
meta, err := util.GetInstanceMetadataFromAuth(req.Auth.Metadata)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid auth metadata: %v", err)
|
||||
}
|
||||
|
||||
instance, err := meta.GetVerifiedInstance(gceClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serviceAccountId, ok := req.Auth.Metadata["service_account_id"]
|
||||
if !ok {
|
||||
return errors.New("invalid auth metadata: service_account_id not found")
|
||||
}
|
||||
if err := b.authorizeGCEInstance(instance, req.Storage, role, meta.Zone, serviceAccountId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateGCEInstance returns an error if the given GCE instance is not authorized for the role.
|
||||
func (b *GcpAuthBackend) authorizeGCEInstance(instance *compute.Instance, s logical.Storage, role *gcpRole, zone, serviceAccountId string) error {
|
||||
gceClient, err := b.GCE(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify instance has role labels if labels were set on role.
|
||||
for k, expectedV := range role.BoundLabels {
|
||||
actualV, ok := instance.Labels[k]
|
||||
if !ok || actualV != expectedV {
|
||||
return fmt.Errorf("role label '%s:%s' not found on GCE instance", k, expectedV)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that instance is in zone or region if given.
|
||||
if len(role.BoundZone) > 0 {
|
||||
var zone string
|
||||
idx := strings.LastIndex(instance.Zone, "zones/")
|
||||
if idx > 0 {
|
||||
// Parse zone name from full zone self-link URL.
|
||||
idx += len("zones/")
|
||||
zone = instance.Zone[idx:len(instance.Zone)]
|
||||
} else {
|
||||
// Expect full zone name to be set as instance zone.
|
||||
zone = instance.Zone
|
||||
}
|
||||
|
||||
if zone != role.BoundZone {
|
||||
return fmt.Errorf("instance is not in role zone '%s'", role.BoundZone)
|
||||
}
|
||||
} else if len(role.BoundRegion) > 0 {
|
||||
zone, err := gceClient.Zones.Get(role.ProjectId, zone).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not verify instance zone '%s' is available for project '%s': %v", role.ProjectId, zone, err)
|
||||
}
|
||||
if zone.Region != role.BoundRegion {
|
||||
return fmt.Errorf("zone '%s' is not in region '%s'", zone.Name, zone.Region)
|
||||
}
|
||||
}
|
||||
|
||||
// If instance group is given, verify group exists and that instance is in group.
|
||||
if len(role.BoundInstanceGroup) > 0 {
|
||||
var group *compute.InstanceGroup
|
||||
var err error
|
||||
|
||||
// Check if group should be zonal or regional.
|
||||
if len(role.BoundZone) > 0 {
|
||||
group, err = gceClient.InstanceGroups.Get(role.ProjectId, role.BoundZone, role.BoundInstanceGroup).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not find role instance group %s (project %s, zone %s)", role.BoundInstanceGroup, role.ProjectId, role.BoundZone)
|
||||
}
|
||||
} else if len(role.BoundRegion) > 0 {
|
||||
group, err = gceClient.RegionInstanceGroups.Get(role.ProjectId, role.BoundRegion, role.BoundInstanceGroup).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not find role instance group %s (project %s, region %s)", role.BoundInstanceGroup, role.ProjectId, role.BoundRegion)
|
||||
}
|
||||
} else {
|
||||
return errors.New("expected zone or region to be set for GCE role '%s' with instance group")
|
||||
}
|
||||
|
||||
// Verify instance group contains authenticating instance.
|
||||
instanceIdFilter := fmt.Sprintf("instance eq %s", instance.SelfLink)
|
||||
listInstanceReq := &compute.InstanceGroupsListInstancesRequest{}
|
||||
listResp, err := gceClient.InstanceGroups.ListInstances(role.ProjectId, role.BoundZone, group.Name, listInstanceReq).Filter(instanceIdFilter).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not confirm instance %s is part of instance group %s: %s", instance.Name, role.BoundInstanceGroup, err)
|
||||
}
|
||||
|
||||
if len(listResp.Items) == 0 {
|
||||
return fmt.Errorf("instance %s is not part of instance group %s", instance.Name, role.BoundInstanceGroup)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Verify instance is running under one of the allowed service accounts.
|
||||
if len(role.BoundServiceAccounts) > 0 {
|
||||
iamClient, err := b.IAM(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serviceAccount, err := util.ServiceAccount(iamClient, serviceAccountId, role.ProjectId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not find service acocunt with id '%s': ")
|
||||
}
|
||||
|
||||
if !(strutil.StrListContains(role.BoundServiceAccounts, serviceAccount.Email) ||
|
||||
strutil.StrListContains(role.BoundServiceAccounts, serviceAccount.UniqueId)) {
|
||||
return fmt.Errorf("GCE instance's service account email (%s) or id (%s) not found in role service accounts: %v",
|
||||
serviceAccount.Email, serviceAccount.UniqueId, role.BoundServiceAccounts)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const pathLoginHelpSyn = `Authenticates Google Cloud Platform entities with Vault.`
|
||||
const pathLoginHelpDesc = `
|
||||
Authenticate Google Cloud Platform (GCP) entities.
|
||||
|
|
|
|||
677
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/path_role.go
generated
vendored
677
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/path_role.go
generated
vendored
|
|
@ -3,80 +3,146 @@ package gcpauth
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault-plugin-auth-gcp/plugin/util"
|
||||
"github.com/hashicorp/vault/helper/policyutil"
|
||||
"github.com/hashicorp/vault/helper/strutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
iamRoleType = "iam"
|
||||
serviceAccountAttr = "service-accounts"
|
||||
serviceAccountWildcard = "*"
|
||||
// Role types
|
||||
iamRoleType = "iam"
|
||||
gceRoleType = "gce"
|
||||
|
||||
// Errors
|
||||
errEmptyRoleName = "role name is required"
|
||||
errEmptyRoleType = "role type cannot be empty"
|
||||
errEmptyProjectId = "project id cannot be empty"
|
||||
errEmptyIamServiceAccounts = "IAM role type must have at least one service account"
|
||||
|
||||
errTemplateEditListWrongType = "role is type '%s', cannot edit attribute '%s' (expected role type: '%s')"
|
||||
errTemplateInvalidRoleTypeArgs = "invalid args found for role of type %s: %s"
|
||||
|
||||
// Other
|
||||
serviceAccountsWildcard = "*"
|
||||
|
||||
// Default duration that JWT tokens must expire within to be accepted (currently only IAM)
|
||||
defaultIamMaxJwtExpMinutes int = 15
|
||||
|
||||
// Max allowed duration that all JWT tokens must expire within to be accepted
|
||||
maxJwtExpMaxMinutes int = 60
|
||||
)
|
||||
|
||||
var baseRoleFieldSchema map[string]*framework.FieldSchema = map[string]*framework.FieldSchema{
|
||||
"name": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Name of the role.",
|
||||
},
|
||||
"type": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Type of the role. Currently supported: iam, gce",
|
||||
},
|
||||
"policies": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: "Policies to be set on tokens issued using this role.",
|
||||
},
|
||||
// Token Limits
|
||||
"ttl": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
Default: 0,
|
||||
Description: `
|
||||
Duration in seconds after which the issued token should expire. Defaults to 0,
|
||||
in which case the value will fallback to the system/mount defaults.`,
|
||||
},
|
||||
"max_ttl": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
Default: 0,
|
||||
Description: "The maximum allowed lifetime of tokens issued using this role.",
|
||||
},
|
||||
"period": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
Default: 0,
|
||||
Description: `
|
||||
If set, indicates that the token generated using this role should never expire. The token should be renewed within the
|
||||
duration specified by this value. At each renewal, the token's TTL will be set to the value of this parameter.`,
|
||||
},
|
||||
// -- GCP Information
|
||||
"project_id": {
|
||||
Type: framework.TypeString,
|
||||
Description: `The id of the project that authorized instances must belong to for this role.`,
|
||||
},
|
||||
"bound_service_accounts": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `
|
||||
Can be set for both 'iam' and 'gce' roles (required for 'iam'). A comma-seperated list of authorized service accounts.
|
||||
If the single value "*" is given, this is assumed to be all service accounts under the role's project. If this
|
||||
is set on a GCE role, the inferred service account from the instance metadata token will be used.`,
|
||||
},
|
||||
"service_accounts": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `Deprecated, use bound_service_accounts instead.`,
|
||||
},
|
||||
}
|
||||
|
||||
var iamOnlyFieldSchema map[string]*framework.FieldSchema = map[string]*framework.FieldSchema{
|
||||
"max_jwt_exp": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
Default: defaultIamMaxJwtExpMinutes * 60,
|
||||
Description: `Currently enabled for 'iam' only. Duration in seconds from time of validation that a JWT must expire within.`,
|
||||
},
|
||||
"allow_gce_inference": {
|
||||
Type: framework.TypeBool,
|
||||
Default: true,
|
||||
Description: `'iam' roles only. If false, Vault will not not allow GCE instances to login in against this role`,
|
||||
},
|
||||
}
|
||||
|
||||
var gceOnlyFieldSchema map[string]*framework.FieldSchema = map[string]*framework.FieldSchema{
|
||||
"bound_zone": {
|
||||
Type: framework.TypeString,
|
||||
Description: `
|
||||
"gce" roles only. If set, determines the zone that a GCE instance must belong to. If a group is provided, it is assumed
|
||||
to be a zonal group and the group must belong to this zone.`,
|
||||
},
|
||||
"bound_region": {
|
||||
Type: framework.TypeString,
|
||||
Description: `
|
||||
"gce" roles only. If set, determines the region that a GCE instance must belong to. If a group is provided, it is
|
||||
assumed to be a regional group and the group must belong to this region. If zone is provided, region will be ignored`,
|
||||
},
|
||||
"bound_instance_group": {
|
||||
Type: framework.TypeString,
|
||||
Description: `"gce" roles only. If set, determines the instance group that an authorized instance must belong to.`,
|
||||
},
|
||||
"bound_labels": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `
|
||||
"gce" roles only. A comma-separated list of Google Cloud Platform labels formatted as "$key:$value" strings that are
|
||||
required for authorized GCE instances.`,
|
||||
},
|
||||
}
|
||||
|
||||
// pathsRole creates paths for listing roles and CRUD operations.
|
||||
func pathsRole(b *GcpAuthBackend) []*framework.Path {
|
||||
roleFieldSchema := map[string]*framework.FieldSchema{}
|
||||
for k, v := range baseRoleFieldSchema {
|
||||
roleFieldSchema[k] = v
|
||||
}
|
||||
for k, v := range iamOnlyFieldSchema {
|
||||
roleFieldSchema[k] = v
|
||||
}
|
||||
for k, v := range gceOnlyFieldSchema {
|
||||
roleFieldSchema[k] = v
|
||||
}
|
||||
|
||||
paths := []*framework.Path{
|
||||
{
|
||||
Pattern: fmt.Sprintf("role/%s", framework.GenericNameRegex("name")),
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Name of the role.",
|
||||
},
|
||||
"type": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Type of the role. Currently supported: iam",
|
||||
},
|
||||
"policies": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Policies to be set on tokens issued using this role.",
|
||||
},
|
||||
"project_id": {
|
||||
Type: framework.TypeString,
|
||||
Description: `The id of the project for service accounts allowed to authenticate to this role.`,
|
||||
},
|
||||
"max_jwt_exp": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
Default: defaultMaxJwtExpMin * 3600,
|
||||
Description: `Duration in seconds from time of validation that a JWT must expire within.`,
|
||||
},
|
||||
// Token Limits
|
||||
"ttl": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
Default: 0,
|
||||
Description: `Duration in seconds after which the issued token should expire. Defaults to 0, in which case the value will fallback to the system/mount defaults.`,
|
||||
},
|
||||
"max_ttl": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
Default: 0,
|
||||
Description: "The maximum allowed lifetime of tokens issued using this role.",
|
||||
},
|
||||
"period": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
Default: 0,
|
||||
Description: `
|
||||
If set, indicates that the token generated using this role should never expire. The token should be renewed within the
|
||||
duration specified by this value. At each renewal, the token's TTL will be set to the value of this parameter.`,
|
||||
},
|
||||
// IAM Role Domain
|
||||
"service_accounts": {
|
||||
Type: framework.TypeString,
|
||||
Description: `
|
||||
A comma-seperated list of service accounts to allow to login as this role. If the single value "*" is given, this
|
||||
is assumed to be all service accounts under the role's project.`,
|
||||
},
|
||||
},
|
||||
|
||||
Pattern: fmt.Sprintf("role/%s", framework.GenericNameRegex("name")),
|
||||
Fields: roleFieldSchema,
|
||||
ExistenceCheck: b.pathRoleExistenceCheck,
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.DeleteOperation: b.pathRoleDelete,
|
||||
logical.ReadOperation: b.pathRoleRead,
|
||||
|
|
@ -89,7 +155,6 @@ is assumed to be all service accounts under the role's project.`,
|
|||
// Paths for listing roles
|
||||
{
|
||||
Pattern: "role/?",
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ListOperation: b.pathRoleList,
|
||||
},
|
||||
|
|
@ -99,7 +164,6 @@ is assumed to be all service accounts under the role's project.`,
|
|||
},
|
||||
{
|
||||
Pattern: "roles/?",
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ListOperation: b.pathRoleList,
|
||||
},
|
||||
|
|
@ -107,9 +171,57 @@ is assumed to be all service accounts under the role's project.`,
|
|||
HelpSynopsis: pathListRolesHelpSyn,
|
||||
HelpDescription: pathListRolesHelpDesc,
|
||||
},
|
||||
|
||||
// Edit service accounts on an IAM role
|
||||
{
|
||||
Pattern: fmt.Sprintf("role/%s/service-accounts", framework.GenericNameRegex("name")),
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Name of the role.",
|
||||
},
|
||||
"add": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: "Service-account emails or IDs to add.",
|
||||
},
|
||||
"remove": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: "Service-account emails or IDs to remove.",
|
||||
},
|
||||
},
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.pathRoleEditIamServiceAccounts,
|
||||
},
|
||||
HelpSynopsis: "Add or remove service accounts for an existing `iam` role",
|
||||
HelpDescription: "Add or remove service accounts from the list bound to an existing `iam` role",
|
||||
},
|
||||
|
||||
// Edit labels on an GCE role
|
||||
{
|
||||
Pattern: fmt.Sprintf("role/%s/labels", framework.GenericNameRegex("name")),
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Name of the role.",
|
||||
},
|
||||
"add": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: "BoundLabels to add (in $key:$value)",
|
||||
},
|
||||
"remove": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: "Label key values to remove",
|
||||
},
|
||||
},
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.pathRoleEditGceLabels,
|
||||
},
|
||||
HelpSynopsis: "Add or remove labels for an existing 'gce' role",
|
||||
HelpDescription: `Add or remove labels for an existing 'gce' role. 'add' labels should be
|
||||
of format '$key:$value' and 'remove' labels should be a list of keys to remove.`,
|
||||
},
|
||||
}
|
||||
|
||||
paths = append(paths, b.pathRoleEditListAttr(serviceAccountAttr)...)
|
||||
return paths
|
||||
}
|
||||
|
||||
|
|
@ -147,22 +259,34 @@ func (b *GcpAuthBackend) pathRoleRead(req *logical.Request, data *framework.Fiel
|
|||
}
|
||||
|
||||
roleMap := map[string]interface{}{
|
||||
"role_type": role.RoleType,
|
||||
"project_id": role.ProjectId,
|
||||
"policies": role.Policies,
|
||||
"max_jwt_exp": int64(role.MaxJwtExp / time.Second),
|
||||
"ttl": int64(role.TTL / time.Second),
|
||||
"max_ttl": int64(role.MaxTTL / time.Second),
|
||||
"period": int64(role.Period / time.Second),
|
||||
"role_type": role.RoleType,
|
||||
"project_id": role.ProjectId,
|
||||
"policies": role.Policies,
|
||||
"ttl": int64(role.TTL / time.Second),
|
||||
"max_ttl": int64(role.MaxTTL / time.Second),
|
||||
"period": int64(role.Period / time.Second),
|
||||
"bound_service_accounts": role.BoundServiceAccounts,
|
||||
}
|
||||
|
||||
switch role.RoleType {
|
||||
case iamRoleType:
|
||||
roleMap["service_accounts"] = role.ServiceAccounts
|
||||
roleMap["max_jwt_exp"] = int64(role.MaxJwtExp / time.Second)
|
||||
roleMap["allow_gce_inference"] = role.AllowGCEInference
|
||||
case gceRoleType:
|
||||
roleMap["bound_zone"] = role.BoundZone
|
||||
roleMap["bound_region"] = role.BoundRegion
|
||||
roleMap["bound_instance_group"] = role.BoundInstanceGroup
|
||||
// Ensure values are not nil to avoid errors during plugin RPC conversions.
|
||||
if role.BoundLabels != nil && len(role.BoundLabels) > 0 {
|
||||
roleMap["bound_labels"] = role.BoundLabels
|
||||
} else {
|
||||
roleMap["bound_labels"] = ""
|
||||
}
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: roleMap}, nil
|
||||
Data: roleMap,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *GcpAuthBackend) pathRoleCreateUpdate(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
|
|
@ -179,16 +303,10 @@ func (b *GcpAuthBackend) pathRoleCreateUpdate(req *logical.Request, data *framew
|
|||
role = &gcpRole{}
|
||||
}
|
||||
|
||||
warnResp, err := role.updateRole(b.System(), req.Operation, data)
|
||||
|
||||
errResp, err := b.storeRole(req.Storage, name, role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if errResp != nil {
|
||||
return errResp, err
|
||||
if err := role.updateRole(b.System(), req.Operation, data); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
||||
return warnResp, nil
|
||||
return b.storeRole(req.Storage, name, role)
|
||||
}
|
||||
|
||||
func (b *GcpAuthBackend) pathRoleList(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
|
|
@ -210,63 +328,29 @@ the authorization token for the instance can access.
|
|||
const pathListRolesHelpSyn = `Lists all the roles that are registered with Vault.`
|
||||
const pathListRolesHelpDesc = `Lists all roles under the GCP backends by name.`
|
||||
|
||||
func (b *GcpAuthBackend) pathRoleEditListAttr(attr string) []*framework.Path {
|
||||
return []*framework.Path{
|
||||
{
|
||||
Pattern: fmt.Sprintf("role/%s/%s", framework.GenericNameRegex("name"), attr),
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Name of the role.",
|
||||
},
|
||||
"add": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: "Values to add.",
|
||||
},
|
||||
"remove": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: "Values to remove.",
|
||||
},
|
||||
},
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.pathRoleEditListOperator(attr),
|
||||
},
|
||||
HelpSynopsis: pathAddToListHelpSyn,
|
||||
HelpDescription: pathAddToListHelpDesc,
|
||||
},
|
||||
func (b *GcpAuthBackend) pathRoleEditIamServiceAccounts(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
roleName := data.Get("name").(string)
|
||||
if roleName == "" {
|
||||
return logical.ErrorResponse(errEmptyRoleName), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *GcpAuthBackend) pathRoleEditListOperator(attr string) framework.OperationFunc {
|
||||
return func(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
roleName := data.Get("name").(string)
|
||||
if roleName == "" {
|
||||
return logical.ErrorResponse(errEmptyRoleName), nil
|
||||
}
|
||||
|
||||
toAdd := data.Get("add").([]string)
|
||||
toRemove := data.Get("remove").([]string)
|
||||
if len(toAdd) == 0 && len(toRemove) == 0 {
|
||||
return logical.ErrorResponse("must provide at least one value to add or remove"), nil
|
||||
}
|
||||
|
||||
role, err := b.role(req.Storage, roleName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch attr {
|
||||
case serviceAccountAttr:
|
||||
if role.RoleType != iamRoleType {
|
||||
return logical.ErrorResponse(fmt.Sprintf("Cannot edit service accounts on non-IAM roles, role is type %s", role.RoleType)), nil
|
||||
}
|
||||
role.ServiceAccounts = editStringValues(role.ServiceAccounts, toAdd, toRemove)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported attribute '%s'", attr)
|
||||
}
|
||||
|
||||
return b.storeRole(req.Storage, roleName, role)
|
||||
toAdd := data.Get("add").([]string)
|
||||
toRemove := data.Get("remove").([]string)
|
||||
if len(toAdd) == 0 && len(toRemove) == 0 {
|
||||
return logical.ErrorResponse("must provide at least one value to add or remove"), nil
|
||||
}
|
||||
|
||||
role, err := b.role(req.Storage, roleName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if role.RoleType != iamRoleType {
|
||||
return logical.ErrorResponse(fmt.Sprintf(errTemplateEditListWrongType, role.RoleType, "service_accounts", iamRoleType)), nil
|
||||
}
|
||||
role.BoundServiceAccounts = editStringValues(role.BoundServiceAccounts, toAdd, toRemove)
|
||||
|
||||
return b.storeRole(req.Storage, roleName, role)
|
||||
}
|
||||
|
||||
func editStringValues(initial []string, toAdd []string, toRemove []string) []string {
|
||||
|
|
@ -294,9 +378,41 @@ func editStringValues(initial []string, toAdd []string, toRemove []string) []str
|
|||
return updated
|
||||
}
|
||||
|
||||
const pathAddToListHelpSyn = `Add and remove values for an list attribute on an existing role`
|
||||
const pathAddToListHelpDesc = `This path allows a user to add values to and/or remove from a list of values
|
||||
for a given role's list attribute`
|
||||
func (b *GcpAuthBackend) pathRoleEditGceLabels(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
roleName := data.Get("name").(string)
|
||||
if roleName == "" {
|
||||
return logical.ErrorResponse(errEmptyRoleName), nil
|
||||
}
|
||||
|
||||
toAdd := data.Get("add").([]string)
|
||||
toRemove := data.Get("remove").([]string)
|
||||
if len(toAdd) == 0 && len(toRemove) == 0 {
|
||||
return logical.ErrorResponse("must provide at least one value to add or remove"), nil
|
||||
}
|
||||
|
||||
role, err := b.role(req.Storage, roleName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if role.RoleType != gceRoleType {
|
||||
return logical.ErrorResponse(fmt.Sprintf(errTemplateEditListWrongType, role.RoleType, "labels", gceRoleType)), nil
|
||||
}
|
||||
|
||||
labelsToAdd, invalidLabels := util.ParseGcpLabels(toAdd)
|
||||
if len(invalidLabels) > 0 {
|
||||
return logical.ErrorResponse(fmt.Sprintf("given invalid labels to add: %q", invalidLabels)), nil
|
||||
}
|
||||
for k, v := range labelsToAdd {
|
||||
role.BoundLabels[k] = v
|
||||
}
|
||||
|
||||
for _, k := range toRemove {
|
||||
delete(role.BoundLabels, k)
|
||||
}
|
||||
|
||||
return b.storeRole(req.Storage, roleName, role)
|
||||
}
|
||||
|
||||
// role reads a gcpRole from storage. This assumes the caller has already obtained the role lock.
|
||||
func (b *GcpAuthBackend) role(s logical.Storage, name string) (*gcpRole, error) {
|
||||
|
|
@ -318,9 +434,18 @@ func (b *GcpAuthBackend) role(s logical.Storage, name string) (*gcpRole, error)
|
|||
}
|
||||
|
||||
// storeRole saves the gcpRole to storage.
|
||||
// The returned response may contain either warnings or an error response,
|
||||
// but will be nil if error is not nil
|
||||
func (b *GcpAuthBackend) storeRole(s logical.Storage, roleName string, role *gcpRole) (*logical.Response, error) {
|
||||
if err := role.validate(b.System()); err != nil {
|
||||
var resp *logical.Response
|
||||
warnings, err := role.validate(b.System())
|
||||
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
} else if len(warnings) > 0 {
|
||||
resp = &logical.Response{
|
||||
Warnings: warnings,
|
||||
}
|
||||
}
|
||||
|
||||
entry, err := logical.StorageEntryJSON(fmt.Sprintf("role/%s", roleName), role)
|
||||
|
|
@ -328,7 +453,11 @@ func (b *GcpAuthBackend) storeRole(s logical.Storage, roleName string, role *gcp
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return nil, s.Put(entry)
|
||||
if err := s.Put(entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type gcpRole struct {
|
||||
|
|
@ -341,9 +470,6 @@ type gcpRole struct {
|
|||
// Policies for Vault to assign to authorized entities.
|
||||
Policies []string `json:"policies" structs:"policies" mapstructure:"policies"`
|
||||
|
||||
// MaxJwtExp is the duration from time of authentication that a JWT used to authenticate to role must expire within.
|
||||
MaxJwtExp time.Duration `json:"max_jwt_exp" structs:"max_jwt_exp" mapstructure:"max_jwt_exp"`
|
||||
|
||||
// TTL of Vault auth leases under this role.
|
||||
TTL time.Duration `json:"ttl" structs:"ttl" mapstructure:"ttl"`
|
||||
|
||||
|
|
@ -355,43 +481,53 @@ type gcpRole struct {
|
|||
// with TTL equal to this value.
|
||||
Period time.Duration `json:"period" structs:"period" mapstructure:"period"`
|
||||
|
||||
// IAM-specific attributes
|
||||
ServiceAccounts []string `json:"service_accounts" structs:"service_accounts" mapstructure:"service_accounts"`
|
||||
// Service accounts allowed to login under this role.
|
||||
BoundServiceAccounts []string `json:"bound_service_accounts" structs:"bound_service_accounts" mapstructure:"bound_service_accounts"`
|
||||
|
||||
// --| IAM-only attributes |--
|
||||
// MaxJwtExp is the duration from time of authentication that a JWT used to authenticate to role must expire within.
|
||||
// TODO(emilymye): Allow this to be updated for GCE roles once 'exp' parameter has been allowed for GCE metadata.
|
||||
MaxJwtExp time.Duration `json:"max_jwt_exp" structs:"max_jwt_exp" mapstructure:"max_jwt_exp"`
|
||||
|
||||
// AllowGCEInference, if false, does not allow a GCE instance to login under this 'iam' role. If true (default),
|
||||
// a service account is inferred from the instance metadata and used as the authenticating instance.
|
||||
AllowGCEInference bool `json:"allow_gce_inference" structs:"allow_gce_inference" mapstructure:"allow_gce_inference"`
|
||||
|
||||
// --| GCE-only attributes |--
|
||||
// BoundRegion that instances must belong to in order to login under this role.
|
||||
BoundRegion string `json:"bound_region" structs:"bound_region" mapstructure:"bound_region"`
|
||||
|
||||
// BoundZone that instances must belong to in order to login under this role.
|
||||
BoundZone string `json:"bound_zone" structs:"bound_zone" mapstructure:"bound_zone"`
|
||||
|
||||
// Instance group that instances must belong to in order to login under this role.
|
||||
BoundInstanceGroup string `json:"bound_instance_group" structs:"bound_instance_group" mapstructure:"bound_instance_group"`
|
||||
|
||||
// BoundLabels that instances must currently have set in order to login under this role.
|
||||
BoundLabels map[string]string `json:"bound_labels" structs:"bound_labels" mapstructure:"bound_labels"`
|
||||
}
|
||||
|
||||
// Update updates the given role with values parsed/validated from given FieldData.
|
||||
// Exactly one of the response and error will be nil. The response is only used to pass back warnings.
|
||||
// This method does not validate the role. Validation is done before storage.
|
||||
func (role *gcpRole) updateRole(sys logical.SystemView, op logical.Operation, data *framework.FieldData) (*logical.Response, error) {
|
||||
warnResp := &logical.Response{}
|
||||
|
||||
func (role *gcpRole) updateRole(sys logical.SystemView, op logical.Operation, data *framework.FieldData) error {
|
||||
// Set role type
|
||||
roleTypeRaw, ok := data.GetOk("type")
|
||||
if ok {
|
||||
if op == logical.UpdateOperation {
|
||||
return nil, errors.New("role type cannot be changed for an existing role")
|
||||
return errors.New("role type cannot be changed for an existing role")
|
||||
}
|
||||
role.RoleType = roleTypeRaw.(string)
|
||||
} else if op == logical.CreateOperation {
|
||||
return nil, errors.New("role type must be provided for a new role")
|
||||
}
|
||||
|
||||
//Update fields specific to this type
|
||||
switch role.RoleType {
|
||||
case iamRoleType:
|
||||
if err := role.updateIamFields(data, op); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("role type '%s' is not supported", role.RoleType)
|
||||
return errors.New(errEmptyRoleType)
|
||||
}
|
||||
|
||||
// Update policies.
|
||||
policies, ok := data.GetOk("policies")
|
||||
if ok {
|
||||
role.Policies = policyutil.ParsePolicies(policies.(string))
|
||||
role.Policies = policyutil.ParsePolicies(policies)
|
||||
} else if op == logical.CreateOperation {
|
||||
role.Policies = policyutil.ParsePolicies(data.Get("policies").(string))
|
||||
role.Policies = policyutil.ParsePolicies(data.Get("policies"))
|
||||
}
|
||||
|
||||
// Update GCP project id.
|
||||
|
|
@ -400,24 +536,11 @@ func (role *gcpRole) updateRole(sys logical.SystemView, op logical.Operation, da
|
|||
role.ProjectId = projectIdRaw.(string)
|
||||
}
|
||||
|
||||
// Update max JWT exp duration.
|
||||
maxJwtExp, ok := data.GetOk("max_jwt_exp")
|
||||
if ok {
|
||||
role.MaxJwtExp = time.Duration(maxJwtExp.(int)) * time.Second
|
||||
} else {
|
||||
role.MaxJwtExp = time.Duration(defaultMaxJwtExpMin) * time.Minute
|
||||
}
|
||||
|
||||
// Update token TTL.
|
||||
ttlRaw, ok := data.GetOk("ttl")
|
||||
if ok {
|
||||
role.TTL = time.Duration(ttlRaw.(int)) * time.Second
|
||||
defaultLeaseTTL := sys.DefaultLeaseTTL()
|
||||
if role.TTL > defaultLeaseTTL {
|
||||
warnResp.AddWarning(fmt.Sprintf(
|
||||
"Given ttl of %d seconds greater than current mount/system default of %d seconds; ttl will be capped at login time",
|
||||
role.TTL/time.Second, defaultLeaseTTL/time.Second))
|
||||
}
|
||||
|
||||
} else if op == logical.CreateOperation {
|
||||
role.TTL = time.Duration(data.Get("ttl").(int)) * time.Second
|
||||
}
|
||||
|
|
@ -426,12 +549,6 @@ func (role *gcpRole) updateRole(sys logical.SystemView, op logical.Operation, da
|
|||
maxTTLRaw, ok := data.GetOk("max_ttl")
|
||||
if ok {
|
||||
role.MaxTTL = time.Duration(maxTTLRaw.(int)) * time.Second
|
||||
systemMaxTTL := sys.MaxLeaseTTL()
|
||||
if role.MaxTTL > systemMaxTTL {
|
||||
warnResp.AddWarning(fmt.Sprintf(
|
||||
"Given max_ttl of %d seconds greater than current mount/system default of %d seconds; max_ttl will be capped at login time",
|
||||
role.MaxTTL/time.Second, systemMaxTTL/time.Second))
|
||||
}
|
||||
} else if op == logical.CreateOperation {
|
||||
role.MaxTTL = time.Duration(data.Get("max_ttl").(int)) * time.Second
|
||||
}
|
||||
|
|
@ -444,68 +561,192 @@ func (role *gcpRole) updateRole(sys logical.SystemView, op logical.Operation, da
|
|||
role.Period = time.Second * time.Duration(data.Get("period").(int))
|
||||
}
|
||||
|
||||
if len(warnResp.Warnings) == 0 {
|
||||
warnResp = nil
|
||||
// Update bound GCP service accounts.
|
||||
serviceAccountsRaw, ok := data.GetOk("bound_service_accounts")
|
||||
if ok {
|
||||
role.BoundServiceAccounts = serviceAccountsRaw.([]string)
|
||||
} else {
|
||||
// Check for older version of param name
|
||||
serviceAccountsRaw, ok := data.GetOk("service_accounts")
|
||||
if ok {
|
||||
role.BoundServiceAccounts = serviceAccountsRaw.([]string)
|
||||
}
|
||||
}
|
||||
return warnResp, nil
|
||||
}
|
||||
|
||||
func (role *gcpRole) validate(sys logical.SystemView) error {
|
||||
// Update fields specific to this type
|
||||
switch role.RoleType {
|
||||
case iamRoleType:
|
||||
if err := role.validateIamFields(); err != nil {
|
||||
if err := checkInvalidRoleTypeArgs(data, gceOnlyFieldSchema); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := role.updateIamFields(data, op); err != nil {
|
||||
return err
|
||||
}
|
||||
case gceRoleType:
|
||||
if err := checkInvalidRoleTypeArgs(data, iamOnlyFieldSchema); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := role.updateGceFields(data, op); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("role type '%s' is invalid", role.RoleType)
|
||||
}
|
||||
|
||||
if len(role.Policies) == 0 {
|
||||
return errors.New("role must have at least one bound policy")
|
||||
}
|
||||
|
||||
if role.ProjectId == "" {
|
||||
return errors.New("role cannot have empty project_id")
|
||||
}
|
||||
|
||||
if role.MaxJwtExp > time.Hour {
|
||||
return errors.New("max_jwt_exp cannot be more than one hour")
|
||||
}
|
||||
|
||||
if role.MaxTTL < time.Duration(0) {
|
||||
return errors.New("max_ttl cannot be negative")
|
||||
}
|
||||
if role.MaxTTL != 0 && role.MaxTTL < role.TTL {
|
||||
return errors.New("ttl should be shorter than max_ttl")
|
||||
}
|
||||
|
||||
if role.Period > sys.MaxLeaseTTL() {
|
||||
return fmt.Errorf("'period' of '%s' is greater than the backend's maximum lease TTL of '%s'", role.Period.String(), sys.MaxLeaseTTL().String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (role *gcpRole) validate(sys logical.SystemView) (warnings []string, err error) {
|
||||
warnings = []string{}
|
||||
|
||||
switch role.RoleType {
|
||||
case iamRoleType:
|
||||
if warnings, err = role.validateForIAM(); err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
case gceRoleType:
|
||||
if warnings, err = role.validateForGCE(); err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
case "":
|
||||
return warnings, errors.New(errEmptyRoleType)
|
||||
default:
|
||||
return warnings, fmt.Errorf("role type '%s' is invalid", role.RoleType)
|
||||
}
|
||||
|
||||
if role.ProjectId == "" {
|
||||
return warnings, errors.New(errEmptyProjectId)
|
||||
}
|
||||
|
||||
defaultLeaseTTL := sys.DefaultLeaseTTL()
|
||||
if role.TTL > defaultLeaseTTL {
|
||||
warnings = append(warnings, fmt.Sprintf(
|
||||
"Given ttl of %d seconds greater than current mount/system default of %d seconds; ttl will be capped at login time",
|
||||
role.TTL/time.Second, defaultLeaseTTL/time.Second))
|
||||
}
|
||||
|
||||
defaultMaxTTL := sys.MaxLeaseTTL()
|
||||
if role.MaxTTL > defaultMaxTTL {
|
||||
warnings = append(warnings, fmt.Sprintf(
|
||||
"Given max_ttl of %d seconds greater than current mount/system default of %d seconds; max_ttl will be capped at login time",
|
||||
role.MaxTTL/time.Second, defaultMaxTTL/time.Second))
|
||||
}
|
||||
if role.MaxTTL < time.Duration(0) {
|
||||
return warnings, errors.New("max_ttl cannot be negative")
|
||||
}
|
||||
if role.MaxTTL != 0 && role.MaxTTL < role.TTL {
|
||||
return warnings, errors.New("ttl should be shorter than max_ttl")
|
||||
}
|
||||
|
||||
if role.Period > sys.MaxLeaseTTL() {
|
||||
return warnings, fmt.Errorf("'period' of '%s' is greater than the backend's maximum lease TTL of '%s'", role.Period.String(), sys.MaxLeaseTTL().String())
|
||||
}
|
||||
|
||||
return warnings, nil
|
||||
}
|
||||
|
||||
// updateIamFields updates IAM-only fields for a role.
|
||||
func (role *gcpRole) updateIamFields(data *framework.FieldData, op logical.Operation) error {
|
||||
serviceAccountsRaw, ok := data.GetOk("service_accounts")
|
||||
allowGCEInference, ok := data.GetOk("allow_gce_inference")
|
||||
if ok {
|
||||
role.ServiceAccounts = strings.Split(serviceAccountsRaw.(string), ",")
|
||||
role.AllowGCEInference = allowGCEInference.(bool)
|
||||
} else if op == logical.CreateOperation {
|
||||
return errors.New(errEmptyIamServiceAccounts)
|
||||
role.AllowGCEInference = data.Get("allow_gce_inference").(bool)
|
||||
}
|
||||
|
||||
maxJwtExp, ok := data.GetOk("max_jwt_exp")
|
||||
if ok {
|
||||
role.MaxJwtExp = time.Duration(maxJwtExp.(int)) * time.Second
|
||||
} else if op == logical.CreateOperation {
|
||||
role.MaxJwtExp = time.Duration(defaultIamMaxJwtExpMinutes) * time.Minute
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateGceFields updates GCE-only fields for a role.
|
||||
func (role *gcpRole) updateGceFields(data *framework.FieldData, op logical.Operation) error {
|
||||
region, hasRegion := data.GetOk("bound_region")
|
||||
if hasRegion {
|
||||
role.BoundRegion = region.(string)
|
||||
}
|
||||
|
||||
zone, hasZone := data.GetOk("bound_zone")
|
||||
if hasZone {
|
||||
role.BoundZone = zone.(string)
|
||||
}
|
||||
|
||||
instanceGroup, ok := data.GetOk("bound_instance_group")
|
||||
if ok {
|
||||
role.BoundInstanceGroup = instanceGroup.(string)
|
||||
}
|
||||
|
||||
labels, ok := data.GetOk("bound_labels")
|
||||
if ok {
|
||||
var invalidLabels []string
|
||||
role.BoundLabels, invalidLabels = util.ParseGcpLabels(labels.([]string))
|
||||
if len(invalidLabels) > 0 {
|
||||
return fmt.Errorf("invalid labels given: %q", invalidLabels)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateIamFields validates the IAM-only fields for a role.
|
||||
func (role *gcpRole) validateIamFields() error {
|
||||
if len(role.ServiceAccounts) == 0 {
|
||||
return errors.New(errEmptyIamServiceAccounts)
|
||||
func (role *gcpRole) validateForIAM() (warnings []string, err error) {
|
||||
if len(role.BoundServiceAccounts) == 0 {
|
||||
return []string{}, errors.New(errEmptyIamServiceAccounts)
|
||||
}
|
||||
|
||||
if len(role.ServiceAccounts) > 1 && strutil.StrListContains(role.ServiceAccounts, serviceAccountWildcard) {
|
||||
return fmt.Errorf("cannot provide IAM service account wildcard '%s' (for all service accounts) with other service accounts", serviceAccountWildcard)
|
||||
if len(role.BoundServiceAccounts) > 1 && strutil.StrListContains(role.BoundServiceAccounts, serviceAccountsWildcard) {
|
||||
return []string{}, fmt.Errorf("cannot provide IAM service account wildcard '%s' (for all service accounts) with other service accounts", serviceAccountsWildcard)
|
||||
}
|
||||
|
||||
maxMaxJwtExp := time.Duration(maxJwtExpMaxMinutes) * time.Minute
|
||||
if role.MaxJwtExp > maxMaxJwtExp {
|
||||
return warnings, fmt.Errorf("max_jwt_exp cannot be more than %d minutes", maxJwtExpMaxMinutes)
|
||||
}
|
||||
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
// validateGceFields validates the GCE-only fields for a role.
|
||||
func (role *gcpRole) validateForGCE() (warnings []string, err error) {
|
||||
warnings = []string{}
|
||||
|
||||
hasRegion := len(role.BoundRegion) > 0
|
||||
hasZone := len(role.BoundZone) > 0
|
||||
hasRegionOrZone := hasRegion || hasZone
|
||||
|
||||
hasInstanceGroup := len(role.BoundInstanceGroup) > 0
|
||||
|
||||
if hasInstanceGroup && !hasRegionOrZone {
|
||||
return warnings, errors.New(`region or zone information must be specified if a group is given`)
|
||||
}
|
||||
|
||||
if hasRegion && hasZone {
|
||||
warnings = append(warnings, "Given both region and zone for role of type 'gce' - region will be ignored.")
|
||||
}
|
||||
|
||||
return warnings, nil
|
||||
}
|
||||
|
||||
// checkInvalidRoleTypeArgs checks that the data provided does not contain arguments
|
||||
// for a different role type. If it does find some, it will return an error with the
|
||||
// invalid args.
|
||||
func checkInvalidRoleTypeArgs(data *framework.FieldData, invalidSchema map[string]*framework.FieldSchema) error {
|
||||
invalidArgs := []string{}
|
||||
|
||||
for k := range data.Raw {
|
||||
if _, ok := baseRoleFieldSchema[k]; ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := invalidSchema[k]; ok {
|
||||
invalidArgs = append(invalidArgs, k)
|
||||
}
|
||||
}
|
||||
|
||||
if len(invalidArgs) > 0 {
|
||||
return fmt.Errorf(errTemplateInvalidRoleTypeArgs, data.Get("type"), strings.Join(invalidArgs, ","))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
126
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/util/computeutil.go
generated
vendored
Normal file
126
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/util/computeutil.go
generated
vendored
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"google.golang.org/api/compute/v1"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CustomJWTClaims struct {
|
||||
Google *GoogleJWTClaims `json:"google,omitempty"`
|
||||
}
|
||||
|
||||
type GoogleJWTClaims struct {
|
||||
Compute *GCEIdentityMetadata `json:"compute_engine,omitempty"`
|
||||
}
|
||||
|
||||
type GCEIdentityMetadata struct {
|
||||
// ProjectId is the ID for the project where you created the instance.
|
||||
ProjectId string `json:"project_id" structs:"project_id" mapstructure:"project_id"`
|
||||
|
||||
// ProjectNumber is the unique ID for the project where you created the instance.
|
||||
ProjectNumber int64 `json:"project_number" structs:"project_number" mapstructure:"project_number"`
|
||||
|
||||
// Zone is the zone where the instance is located.
|
||||
Zone string `json:"zone" structs:"zone" mapstructure:"zone"`
|
||||
|
||||
// InstanceId is the unique ID for the instance to which this token belongs. This ID is unique and never reused.
|
||||
InstanceId string `json:"instance_id" structs:"instance_id" mapstructure:"instance_id"`
|
||||
|
||||
// InstanceName is the name of the instance to which this token belongs. This name can be reused by several
|
||||
// instances over time, so use the instance_id value to identify a unique instance ID.
|
||||
InstanceName string `json:"instance_name" structs:"instance_name" mapstructure:"instance_name"`
|
||||
|
||||
// CreatedAt is a unix timestamp indicating when you created the instance.
|
||||
CreatedAt int64 `json:"instance_creation_timestamp" structs:"instance_creation_timestamp" mapstructure:"instance_creation_timestamp"`
|
||||
}
|
||||
|
||||
// GetVerifiedInstance returns the Instance as described by the identity metadata or an error.
|
||||
// If the instance has an invalid status or its creation timestamp does not match the metadata value,
|
||||
// this will return nil and an error.
|
||||
func (meta *GCEIdentityMetadata) GetVerifiedInstance(gceClient *compute.Service) (*compute.Instance, error) {
|
||||
instance, err := gceClient.Instances.Get(meta.ProjectId, meta.Zone, meta.InstanceName).Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to find instance associated with token: %v", err)
|
||||
}
|
||||
|
||||
if !IsValidInstanceStatus(instance.Status) {
|
||||
return nil, fmt.Errorf("authenticating instance %s found but has invalid status '%s'", instance.Name, instance.Status)
|
||||
}
|
||||
|
||||
// Parse the metadata CreatedAt into time.
|
||||
metaTime := time.Unix(meta.CreatedAt, 0)
|
||||
|
||||
// Parse instance creationTimestamp into time.
|
||||
actualTime, err := time.Parse(time.RFC3339Nano, instance.CreationTimestamp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("instance 'creationTimestamp' field could not be parsed into time: %s", instance.CreationTimestamp)
|
||||
}
|
||||
|
||||
// Return an error if the metadata creation timestamp is before the instance creation timestamp.
|
||||
delta := float64(metaTime.Sub(actualTime)) / float64(time.Second)
|
||||
if delta < -1 {
|
||||
return nil, fmt.Errorf("metadata instance_creation_timestamp %d is before instance's creation time %d", actualTime.Unix(), metaTime.Unix())
|
||||
}
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
var validInstanceStates map[string]struct{} = map[string]struct{}{
|
||||
"PROVISIONING": struct{}{},
|
||||
"RUNNING": struct{}{},
|
||||
"STAGING": struct{}{},
|
||||
}
|
||||
|
||||
func IsValidInstanceStatus(status string) bool {
|
||||
_, ok := validInstanceStates[status]
|
||||
return ok
|
||||
}
|
||||
|
||||
func GetInstanceMetadataFromAuth(authMetadata map[string]string) (*GCEIdentityMetadata, error) {
|
||||
meta := &GCEIdentityMetadata{}
|
||||
var ok bool
|
||||
var err error
|
||||
|
||||
meta.ProjectId, ok = authMetadata["project_id"]
|
||||
if !ok {
|
||||
return nil, errors.New("expected 'project_id' field")
|
||||
}
|
||||
|
||||
meta.Zone, ok = authMetadata["zone"]
|
||||
if !ok {
|
||||
return nil, errors.New("expected 'zone' field")
|
||||
}
|
||||
|
||||
meta.InstanceId, ok = authMetadata["instance_id"]
|
||||
if !ok {
|
||||
return nil, errors.New("expected 'instance_id' field")
|
||||
}
|
||||
|
||||
meta.InstanceName, ok = authMetadata["instance_name"]
|
||||
if !ok {
|
||||
return nil, errors.New("expected 'instance_name' field")
|
||||
}
|
||||
|
||||
// Parse numbers back into int values.
|
||||
projectNumber, ok := authMetadata["project_number"]
|
||||
if !ok {
|
||||
return nil, errors.New("expected 'project_number' field, got %v")
|
||||
}
|
||||
meta.ProjectNumber, err = strconv.ParseInt(projectNumber, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("expected 'project_number' value '%s' to be a int64", projectNumber)
|
||||
}
|
||||
|
||||
createdAt, ok := authMetadata["instance_creation_timestamp"]
|
||||
if !ok {
|
||||
return nil, errors.New("expected 'instance_creation_timestamp' field")
|
||||
}
|
||||
meta.CreatedAt, err = strconv.ParseInt(createdAt, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("expected 'instance_creation_timestamp' value '%s' to be int64", createdAt)
|
||||
}
|
||||
|
||||
return meta, nil
|
||||
}
|
||||
|
|
@ -7,10 +7,19 @@ import (
|
|||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hashicorp/go-cleanhttp"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
googleoauth2 "google.golang.org/api/oauth2/v2"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
labelRegex string = "^(?P<key>[a-z]([\\w-]+)?):(?P<value>[\\w-]*)$"
|
||||
)
|
||||
|
||||
// GcpCredentials represents a simplified version of the Google Cloud Platform credentials file format.
|
||||
|
|
@ -64,3 +73,72 @@ func PublicKey(pemString string) (interface{}, error) {
|
|||
|
||||
return cert.PublicKey, nil
|
||||
}
|
||||
|
||||
// OAuth2RSAPublicKey returns the PEM key file string for Google Oauth2 public cert for the given 'kid' id.
|
||||
func OAuth2RSAPublicKey(kid, oauth2BasePath string) (interface{}, error) {
|
||||
oauth2Client, err := googleoauth2.New(cleanhttp.DefaultClient())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(oauth2BasePath) > 0 {
|
||||
oauth2Client.BasePath = oauth2BasePath
|
||||
}
|
||||
|
||||
jwks, err := oauth2Client.GetCertForOpenIdConnect().Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, key := range jwks.Keys {
|
||||
if key.Kid == kid && jose.SignatureAlgorithm(key.Alg) == jose.RS256 {
|
||||
// Trim extra '=' from key so it can be parsed.
|
||||
key.N = strings.TrimRight(key.N, "=")
|
||||
js, err := key.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to marshal json %v", err)
|
||||
}
|
||||
key := &jose.JSONWebKey{}
|
||||
if err := key.UnmarshalJSON(js); err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshal json %v", err)
|
||||
}
|
||||
|
||||
return key.Key, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("could not find public key with kid '%s'", kid)
|
||||
}
|
||||
|
||||
func ParseGcpLabels(labels []string) (parsed map[string]string, invalid []string) {
|
||||
parsed = map[string]string{}
|
||||
invalid = []string{}
|
||||
|
||||
re := regexp.MustCompile(labelRegex)
|
||||
for _, labelStr := range labels {
|
||||
matches := re.FindStringSubmatch(labelStr)
|
||||
if len(matches) == 0 {
|
||||
invalid = append(invalid, labelStr)
|
||||
continue
|
||||
}
|
||||
|
||||
captureNames := re.SubexpNames()
|
||||
var keyPtr, valPtr *string
|
||||
for i, name := range captureNames {
|
||||
if name == "key" {
|
||||
keyPtr = &matches[i]
|
||||
} else if name == "value" {
|
||||
valPtr = &matches[i]
|
||||
}
|
||||
}
|
||||
|
||||
if keyPtr == nil || valPtr == nil || len(*keyPtr) < 1 {
|
||||
invalid = append(invalid, labelStr)
|
||||
continue
|
||||
} else {
|
||||
parsed[*keyPtr] = *valPtr
|
||||
}
|
||||
}
|
||||
|
||||
return parsed, invalid
|
||||
}
|
||||
23
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/util/iamutil.go
generated
vendored
23
vendor/github.com/hashicorp/vault-plugin-auth-gcp/plugin/util/iamutil.go
generated
vendored
|
|
@ -2,9 +2,14 @@ package util
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"google.golang.org/api/iam/v1"
|
||||
"time"
|
||||
|
||||
// TODO(emilymye): Currently square's JOSE library doesn't allow for obtaining claims without providing a key.
|
||||
// Replace SermoDigital in this file once it is possible to verify the JWT using claims.
|
||||
"github.com/SermoDigital/jose/jws"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -35,20 +40,32 @@ func ServiceAccountLoginJwt(
|
|||
func ServiceAccount(iamClient *iam.Service, accountId, projectName string) (*iam.ServiceAccount, error) {
|
||||
accountResource := fmt.Sprintf(serviceAccountTemplate, projectName, accountId)
|
||||
account, err := iamClient.Projects.ServiceAccounts.Get(accountResource).Do()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("service account '%s' does not exist", accountResource)
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// serviceAccount wraps a call to the GCP IAM API to get a service account key.
|
||||
// ServiceAccountKey wraps a call to the GCP IAM API to get a service account key.
|
||||
func ServiceAccountKey(iamClient *iam.Service, keyId, accountId, projectName string) (*iam.ServiceAccountKey, error) {
|
||||
keyResource := fmt.Sprintf(serviceAccountKeyTemplate, projectName, accountId, keyId)
|
||||
key, err := iamClient.Projects.ServiceAccounts.Keys.Get(keyResource).PublicKeyType(serviceAccountKeyFileType).Do()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("service account key '%s' does not exist", keyResource)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// ParseServiceAccountFromIAMJWT parses the service account from the 'sub' claim given a serialized signed JWT.
|
||||
func ParseServiceAccountFromIAMJWT(signedJwt string) (string, error) {
|
||||
jwtVal, err := jws.ParseJWT([]byte(signedJwt))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not parse service account from JWT 'sub' claim: %v", err)
|
||||
}
|
||||
accountId, ok := jwtVal.Claims().Subject()
|
||||
if !ok {
|
||||
return "", errors.New("expected 'sub' claim with service account ID or name")
|
||||
}
|
||||
return accountId, nil
|
||||
}
|
||||
|
|
|
|||
22099
vendor/google.golang.org/api/compute/v1/compute-api.json
generated
vendored
Normal file
22099
vendor/google.golang.org/api/compute/v1/compute-api.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
69480
vendor/google.golang.org/api/compute/v1/compute-gen.go
generated
vendored
Normal file
69480
vendor/google.golang.org/api/compute/v1/compute-gen.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
294
vendor/google.golang.org/api/oauth2/v2/oauth2-api.json
generated
vendored
Normal file
294
vendor/google.golang.org/api/oauth2/v2/oauth2-api.json
generated
vendored
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
{
|
||||
"kind": "discovery#restDescription",
|
||||
"etag": "\"YWOzh2SDasdU84ArJnpYek-OMdg/UI_PjeJG3puXfKEXm-20UHWIhYQ\"",
|
||||
"discoveryVersion": "v1",
|
||||
"id": "oauth2:v2",
|
||||
"name": "oauth2",
|
||||
"version": "v2",
|
||||
"revision": "20170807",
|
||||
"title": "Google OAuth2 API",
|
||||
"description": "Obtains end-user authorization grants for use with other Google APIs.",
|
||||
"ownerDomain": "google.com",
|
||||
"ownerName": "Google",
|
||||
"icons": {
|
||||
"x16": "https://www.gstatic.com/images/branding/product/1x/googleg_16dp.png",
|
||||
"x32": "https://www.gstatic.com/images/branding/product/1x/googleg_32dp.png"
|
||||
},
|
||||
"documentationLink": "https://developers.google.com/accounts/docs/OAuth2",
|
||||
"protocol": "rest",
|
||||
"baseUrl": "https://www.googleapis.com/",
|
||||
"basePath": "/",
|
||||
"rootUrl": "https://www.googleapis.com/",
|
||||
"servicePath": "",
|
||||
"batchPath": "batch/oauth2/v2",
|
||||
"parameters": {
|
||||
"alt": {
|
||||
"type": "string",
|
||||
"description": "Data format for the response.",
|
||||
"default": "json",
|
||||
"enum": [
|
||||
"json"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Responses with Content-Type of application/json"
|
||||
],
|
||||
"location": "query"
|
||||
},
|
||||
"fields": {
|
||||
"type": "string",
|
||||
"description": "Selector specifying which fields to include in a partial response.",
|
||||
"location": "query"
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
|
||||
"location": "query"
|
||||
},
|
||||
"oauth_token": {
|
||||
"type": "string",
|
||||
"description": "OAuth 2.0 token for the current user.",
|
||||
"location": "query"
|
||||
},
|
||||
"prettyPrint": {
|
||||
"type": "boolean",
|
||||
"description": "Returns response with indentations and line breaks.",
|
||||
"default": "true",
|
||||
"location": "query"
|
||||
},
|
||||
"quotaUser": {
|
||||
"type": "string",
|
||||
"description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided.",
|
||||
"location": "query"
|
||||
},
|
||||
"userIp": {
|
||||
"type": "string",
|
||||
"description": "IP address of the site where the request originates. Use this if you want to enforce per-user limits.",
|
||||
"location": "query"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"oauth2": {
|
||||
"scopes": {
|
||||
"https://www.googleapis.com/auth/plus.login": {
|
||||
"description": "Know the list of people in your circles, your age range, and language"
|
||||
},
|
||||
"https://www.googleapis.com/auth/plus.me": {
|
||||
"description": "Know who you are on Google"
|
||||
},
|
||||
"https://www.googleapis.com/auth/userinfo.email": {
|
||||
"description": "View your email address"
|
||||
},
|
||||
"https://www.googleapis.com/auth/userinfo.profile": {
|
||||
"description": "View your basic profile info"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"schemas": {
|
||||
"Jwk": {
|
||||
"id": "Jwk",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"keys": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alg": {
|
||||
"type": "string",
|
||||
"default": "RS256"
|
||||
},
|
||||
"e": {
|
||||
"type": "string"
|
||||
},
|
||||
"kid": {
|
||||
"type": "string"
|
||||
},
|
||||
"kty": {
|
||||
"type": "string",
|
||||
"default": "RSA"
|
||||
},
|
||||
"n": {
|
||||
"type": "string"
|
||||
},
|
||||
"use": {
|
||||
"type": "string",
|
||||
"default": "sig"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Tokeninfo": {
|
||||
"id": "Tokeninfo",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"access_type": {
|
||||
"type": "string",
|
||||
"description": "The access type granted with this token. It can be offline or online."
|
||||
},
|
||||
"audience": {
|
||||
"type": "string",
|
||||
"description": "Who is the intended audience for this token. In general the same as issued_to."
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "The email address of the user. Present only if the email scope is present in the request."
|
||||
},
|
||||
"expires_in": {
|
||||
"type": "integer",
|
||||
"description": "The expiry time of the token, as number of seconds left until expiry.",
|
||||
"format": "int32"
|
||||
},
|
||||
"issued_to": {
|
||||
"type": "string",
|
||||
"description": "To whom was the token issued to. In general the same as audience."
|
||||
},
|
||||
"scope": {
|
||||
"type": "string",
|
||||
"description": "The space separated list of scopes granted to this token."
|
||||
},
|
||||
"token_handle": {
|
||||
"type": "string",
|
||||
"description": "The token handle associated with this token."
|
||||
},
|
||||
"user_id": {
|
||||
"type": "string",
|
||||
"description": "The obfuscated user id."
|
||||
},
|
||||
"verified_email": {
|
||||
"type": "boolean",
|
||||
"description": "Boolean flag which is true if the email address is verified. Present only if the email scope is present in the request."
|
||||
}
|
||||
}
|
||||
},
|
||||
"Userinfoplus": {
|
||||
"id": "Userinfoplus",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "The user's email address."
|
||||
},
|
||||
"family_name": {
|
||||
"type": "string",
|
||||
"description": "The user's last name."
|
||||
},
|
||||
"gender": {
|
||||
"type": "string",
|
||||
"description": "The user's gender."
|
||||
},
|
||||
"given_name": {
|
||||
"type": "string",
|
||||
"description": "The user's first name."
|
||||
},
|
||||
"hd": {
|
||||
"type": "string",
|
||||
"description": "The hosted domain e.g. example.com if the user is Google apps user."
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The obfuscated ID of the user."
|
||||
},
|
||||
"link": {
|
||||
"type": "string",
|
||||
"description": "URL of the profile page."
|
||||
},
|
||||
"locale": {
|
||||
"type": "string",
|
||||
"description": "The user's preferred locale."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The user's full name."
|
||||
},
|
||||
"picture": {
|
||||
"type": "string",
|
||||
"description": "URL of the user's picture image."
|
||||
},
|
||||
"verified_email": {
|
||||
"type": "boolean",
|
||||
"description": "Boolean flag which is true if the email address is verified. Always verified because we only return the user's primary email address.",
|
||||
"default": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"methods": {
|
||||
"getCertForOpenIdConnect": {
|
||||
"id": "oauth2.getCertForOpenIdConnect",
|
||||
"path": "oauth2/v2/certs",
|
||||
"httpMethod": "GET",
|
||||
"response": {
|
||||
"$ref": "Jwk"
|
||||
}
|
||||
},
|
||||
"tokeninfo": {
|
||||
"id": "oauth2.tokeninfo",
|
||||
"path": "oauth2/v2/tokeninfo",
|
||||
"httpMethod": "POST",
|
||||
"parameters": {
|
||||
"access_token": {
|
||||
"type": "string",
|
||||
"location": "query"
|
||||
},
|
||||
"id_token": {
|
||||
"type": "string",
|
||||
"location": "query"
|
||||
},
|
||||
"token_handle": {
|
||||
"type": "string",
|
||||
"location": "query"
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"$ref": "Tokeninfo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"userinfo": {
|
||||
"methods": {
|
||||
"get": {
|
||||
"id": "oauth2.userinfo.get",
|
||||
"path": "oauth2/v2/userinfo",
|
||||
"httpMethod": "GET",
|
||||
"response": {
|
||||
"$ref": "Userinfoplus"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/plus.login",
|
||||
"https://www.googleapis.com/auth/plus.me",
|
||||
"https://www.googleapis.com/auth/userinfo.email",
|
||||
"https://www.googleapis.com/auth/userinfo.profile"
|
||||
]
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"v2": {
|
||||
"resources": {
|
||||
"me": {
|
||||
"methods": {
|
||||
"get": {
|
||||
"id": "oauth2.userinfo.v2.me.get",
|
||||
"path": "userinfo/v2/me",
|
||||
"httpMethod": "GET",
|
||||
"response": {
|
||||
"$ref": "Userinfoplus"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/plus.login",
|
||||
"https://www.googleapis.com/auth/plus.me",
|
||||
"https://www.googleapis.com/auth/userinfo.email",
|
||||
"https://www.googleapis.com/auth/userinfo.profile"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
809
vendor/google.golang.org/api/oauth2/v2/oauth2-gen.go
generated
vendored
Normal file
809
vendor/google.golang.org/api/oauth2/v2/oauth2-gen.go
generated
vendored
Normal file
|
|
@ -0,0 +1,809 @@
|
|||
// Package oauth2 provides access to the Google OAuth2 API.
|
||||
//
|
||||
// See https://developers.google.com/accounts/docs/OAuth2
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// import "google.golang.org/api/oauth2/v2"
|
||||
// ...
|
||||
// oauth2Service, err := oauth2.New(oauthHttpClient)
|
||||
package oauth2 // import "google.golang.org/api/oauth2/v2"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
context "golang.org/x/net/context"
|
||||
ctxhttp "golang.org/x/net/context/ctxhttp"
|
||||
gensupport "google.golang.org/api/gensupport"
|
||||
googleapi "google.golang.org/api/googleapi"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Always reference these packages, just in case the auto-generated code
|
||||
// below doesn't.
|
||||
var _ = bytes.NewBuffer
|
||||
var _ = strconv.Itoa
|
||||
var _ = fmt.Sprintf
|
||||
var _ = json.NewDecoder
|
||||
var _ = io.Copy
|
||||
var _ = url.Parse
|
||||
var _ = gensupport.MarshalJSON
|
||||
var _ = googleapi.Version
|
||||
var _ = errors.New
|
||||
var _ = strings.Replace
|
||||
var _ = context.Canceled
|
||||
var _ = ctxhttp.Do
|
||||
|
||||
const apiId = "oauth2:v2"
|
||||
const apiName = "oauth2"
|
||||
const apiVersion = "v2"
|
||||
const basePath = "https://www.googleapis.com/"
|
||||
|
||||
// OAuth2 scopes used by this API.
|
||||
const (
|
||||
// Know the list of people in your circles, your age range, and language
|
||||
PlusLoginScope = "https://www.googleapis.com/auth/plus.login"
|
||||
|
||||
// Know who you are on Google
|
||||
PlusMeScope = "https://www.googleapis.com/auth/plus.me"
|
||||
|
||||
// View your email address
|
||||
UserinfoEmailScope = "https://www.googleapis.com/auth/userinfo.email"
|
||||
|
||||
// View your basic profile info
|
||||
UserinfoProfileScope = "https://www.googleapis.com/auth/userinfo.profile"
|
||||
)
|
||||
|
||||
func New(client *http.Client) (*Service, error) {
|
||||
if client == nil {
|
||||
return nil, errors.New("client is nil")
|
||||
}
|
||||
s := &Service{client: client, BasePath: basePath}
|
||||
s.Userinfo = NewUserinfoService(s)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
client *http.Client
|
||||
BasePath string // API endpoint base URL
|
||||
UserAgent string // optional additional User-Agent fragment
|
||||
|
||||
Userinfo *UserinfoService
|
||||
}
|
||||
|
||||
func (s *Service) userAgent() string {
|
||||
if s.UserAgent == "" {
|
||||
return googleapi.UserAgent
|
||||
}
|
||||
return googleapi.UserAgent + " " + s.UserAgent
|
||||
}
|
||||
|
||||
func NewUserinfoService(s *Service) *UserinfoService {
|
||||
rs := &UserinfoService{s: s}
|
||||
rs.V2 = NewUserinfoV2Service(s)
|
||||
return rs
|
||||
}
|
||||
|
||||
type UserinfoService struct {
|
||||
s *Service
|
||||
|
||||
V2 *UserinfoV2Service
|
||||
}
|
||||
|
||||
func NewUserinfoV2Service(s *Service) *UserinfoV2Service {
|
||||
rs := &UserinfoV2Service{s: s}
|
||||
rs.Me = NewUserinfoV2MeService(s)
|
||||
return rs
|
||||
}
|
||||
|
||||
type UserinfoV2Service struct {
|
||||
s *Service
|
||||
|
||||
Me *UserinfoV2MeService
|
||||
}
|
||||
|
||||
func NewUserinfoV2MeService(s *Service) *UserinfoV2MeService {
|
||||
rs := &UserinfoV2MeService{s: s}
|
||||
return rs
|
||||
}
|
||||
|
||||
type UserinfoV2MeService struct {
|
||||
s *Service
|
||||
}
|
||||
|
||||
type Jwk struct {
|
||||
Keys []*JwkKeys `json:"keys,omitempty"`
|
||||
|
||||
// ServerResponse contains the HTTP response code and headers from the
|
||||
// server.
|
||||
googleapi.ServerResponse `json:"-"`
|
||||
|
||||
// ForceSendFields is a list of field names (e.g. "Keys") to
|
||||
// unconditionally include in API requests. By default, fields with
|
||||
// empty values are omitted from API requests. However, any non-pointer,
|
||||
// non-interface field appearing in ForceSendFields will be sent to the
|
||||
// server regardless of whether the field is empty or not. This may be
|
||||
// used to include empty fields in Patch requests.
|
||||
ForceSendFields []string `json:"-"`
|
||||
|
||||
// NullFields is a list of field names (e.g. "Keys") to include in API
|
||||
// requests with the JSON null value. By default, fields with empty
|
||||
// values are omitted from API requests. However, any field with an
|
||||
// empty value appearing in NullFields will be sent to the server as
|
||||
// null. It is an error if a field in this list has a non-empty value.
|
||||
// This may be used to include null fields in Patch requests.
|
||||
NullFields []string `json:"-"`
|
||||
}
|
||||
|
||||
func (s *Jwk) MarshalJSON() ([]byte, error) {
|
||||
type noMethod Jwk
|
||||
raw := noMethod(*s)
|
||||
return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields)
|
||||
}
|
||||
|
||||
type JwkKeys struct {
|
||||
Alg string `json:"alg,omitempty"`
|
||||
|
||||
E string `json:"e,omitempty"`
|
||||
|
||||
Kid string `json:"kid,omitempty"`
|
||||
|
||||
Kty string `json:"kty,omitempty"`
|
||||
|
||||
N string `json:"n,omitempty"`
|
||||
|
||||
Use string `json:"use,omitempty"`
|
||||
|
||||
// ForceSendFields is a list of field names (e.g. "Alg") to
|
||||
// unconditionally include in API requests. By default, fields with
|
||||
// empty values are omitted from API requests. However, any non-pointer,
|
||||
// non-interface field appearing in ForceSendFields will be sent to the
|
||||
// server regardless of whether the field is empty or not. This may be
|
||||
// used to include empty fields in Patch requests.
|
||||
ForceSendFields []string `json:"-"`
|
||||
|
||||
// NullFields is a list of field names (e.g. "Alg") to include in API
|
||||
// requests with the JSON null value. By default, fields with empty
|
||||
// values are omitted from API requests. However, any field with an
|
||||
// empty value appearing in NullFields will be sent to the server as
|
||||
// null. It is an error if a field in this list has a non-empty value.
|
||||
// This may be used to include null fields in Patch requests.
|
||||
NullFields []string `json:"-"`
|
||||
}
|
||||
|
||||
func (s *JwkKeys) MarshalJSON() ([]byte, error) {
|
||||
type noMethod JwkKeys
|
||||
raw := noMethod(*s)
|
||||
return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields)
|
||||
}
|
||||
|
||||
type Tokeninfo struct {
|
||||
// AccessType: The access type granted with this token. It can be
|
||||
// offline or online.
|
||||
AccessType string `json:"access_type,omitempty"`
|
||||
|
||||
// Audience: Who is the intended audience for this token. In general the
|
||||
// same as issued_to.
|
||||
Audience string `json:"audience,omitempty"`
|
||||
|
||||
// Email: The email address of the user. Present only if the email scope
|
||||
// is present in the request.
|
||||
Email string `json:"email,omitempty"`
|
||||
|
||||
// ExpiresIn: The expiry time of the token, as number of seconds left
|
||||
// until expiry.
|
||||
ExpiresIn int64 `json:"expires_in,omitempty"`
|
||||
|
||||
// IssuedTo: To whom was the token issued to. In general the same as
|
||||
// audience.
|
||||
IssuedTo string `json:"issued_to,omitempty"`
|
||||
|
||||
// Scope: The space separated list of scopes granted to this token.
|
||||
Scope string `json:"scope,omitempty"`
|
||||
|
||||
// TokenHandle: The token handle associated with this token.
|
||||
TokenHandle string `json:"token_handle,omitempty"`
|
||||
|
||||
// UserId: The obfuscated user id.
|
||||
UserId string `json:"user_id,omitempty"`
|
||||
|
||||
// VerifiedEmail: Boolean flag which is true if the email address is
|
||||
// verified. Present only if the email scope is present in the request.
|
||||
VerifiedEmail bool `json:"verified_email,omitempty"`
|
||||
|
||||
// ServerResponse contains the HTTP response code and headers from the
|
||||
// server.
|
||||
googleapi.ServerResponse `json:"-"`
|
||||
|
||||
// ForceSendFields is a list of field names (e.g. "AccessType") to
|
||||
// unconditionally include in API requests. By default, fields with
|
||||
// empty values are omitted from API requests. However, any non-pointer,
|
||||
// non-interface field appearing in ForceSendFields will be sent to the
|
||||
// server regardless of whether the field is empty or not. This may be
|
||||
// used to include empty fields in Patch requests.
|
||||
ForceSendFields []string `json:"-"`
|
||||
|
||||
// NullFields is a list of field names (e.g. "AccessType") to include in
|
||||
// API requests with the JSON null value. By default, fields with empty
|
||||
// values are omitted from API requests. However, any field with an
|
||||
// empty value appearing in NullFields will be sent to the server as
|
||||
// null. It is an error if a field in this list has a non-empty value.
|
||||
// This may be used to include null fields in Patch requests.
|
||||
NullFields []string `json:"-"`
|
||||
}
|
||||
|
||||
func (s *Tokeninfo) MarshalJSON() ([]byte, error) {
|
||||
type noMethod Tokeninfo
|
||||
raw := noMethod(*s)
|
||||
return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields)
|
||||
}
|
||||
|
||||
type Userinfoplus struct {
|
||||
// Email: The user's email address.
|
||||
Email string `json:"email,omitempty"`
|
||||
|
||||
// FamilyName: The user's last name.
|
||||
FamilyName string `json:"family_name,omitempty"`
|
||||
|
||||
// Gender: The user's gender.
|
||||
Gender string `json:"gender,omitempty"`
|
||||
|
||||
// GivenName: The user's first name.
|
||||
GivenName string `json:"given_name,omitempty"`
|
||||
|
||||
// Hd: The hosted domain e.g. example.com if the user is Google apps
|
||||
// user.
|
||||
Hd string `json:"hd,omitempty"`
|
||||
|
||||
// Id: The obfuscated ID of the user.
|
||||
Id string `json:"id,omitempty"`
|
||||
|
||||
// Link: URL of the profile page.
|
||||
Link string `json:"link,omitempty"`
|
||||
|
||||
// Locale: The user's preferred locale.
|
||||
Locale string `json:"locale,omitempty"`
|
||||
|
||||
// Name: The user's full name.
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// Picture: URL of the user's picture image.
|
||||
Picture string `json:"picture,omitempty"`
|
||||
|
||||
// VerifiedEmail: Boolean flag which is true if the email address is
|
||||
// verified. Always verified because we only return the user's primary
|
||||
// email address.
|
||||
//
|
||||
// Default: true
|
||||
VerifiedEmail *bool `json:"verified_email,omitempty"`
|
||||
|
||||
// ServerResponse contains the HTTP response code and headers from the
|
||||
// server.
|
||||
googleapi.ServerResponse `json:"-"`
|
||||
|
||||
// ForceSendFields is a list of field names (e.g. "Email") to
|
||||
// unconditionally include in API requests. By default, fields with
|
||||
// empty values are omitted from API requests. However, any non-pointer,
|
||||
// non-interface field appearing in ForceSendFields will be sent to the
|
||||
// server regardless of whether the field is empty or not. This may be
|
||||
// used to include empty fields in Patch requests.
|
||||
ForceSendFields []string `json:"-"`
|
||||
|
||||
// NullFields is a list of field names (e.g. "Email") to include in API
|
||||
// requests with the JSON null value. By default, fields with empty
|
||||
// values are omitted from API requests. However, any field with an
|
||||
// empty value appearing in NullFields will be sent to the server as
|
||||
// null. It is an error if a field in this list has a non-empty value.
|
||||
// This may be used to include null fields in Patch requests.
|
||||
NullFields []string `json:"-"`
|
||||
}
|
||||
|
||||
func (s *Userinfoplus) MarshalJSON() ([]byte, error) {
|
||||
type noMethod Userinfoplus
|
||||
raw := noMethod(*s)
|
||||
return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields)
|
||||
}
|
||||
|
||||
// method id "oauth2.getCertForOpenIdConnect":
|
||||
|
||||
type GetCertForOpenIdConnectCall struct {
|
||||
s *Service
|
||||
urlParams_ gensupport.URLParams
|
||||
ifNoneMatch_ string
|
||||
ctx_ context.Context
|
||||
header_ http.Header
|
||||
}
|
||||
|
||||
// GetCertForOpenIdConnect:
|
||||
func (s *Service) GetCertForOpenIdConnect() *GetCertForOpenIdConnectCall {
|
||||
c := &GetCertForOpenIdConnectCall{s: s, urlParams_: make(gensupport.URLParams)}
|
||||
return c
|
||||
}
|
||||
|
||||
// Fields allows partial responses to be retrieved. See
|
||||
// https://developers.google.com/gdata/docs/2.0/basics#PartialResponse
|
||||
// for more information.
|
||||
func (c *GetCertForOpenIdConnectCall) Fields(s ...googleapi.Field) *GetCertForOpenIdConnectCall {
|
||||
c.urlParams_.Set("fields", googleapi.CombineFields(s))
|
||||
return c
|
||||
}
|
||||
|
||||
// IfNoneMatch sets the optional parameter which makes the operation
|
||||
// fail if the object's ETag matches the given value. This is useful for
|
||||
// getting updates only after the object has changed since the last
|
||||
// request. Use googleapi.IsNotModified to check whether the response
|
||||
// error from Do is the result of In-None-Match.
|
||||
func (c *GetCertForOpenIdConnectCall) IfNoneMatch(entityTag string) *GetCertForOpenIdConnectCall {
|
||||
c.ifNoneMatch_ = entityTag
|
||||
return c
|
||||
}
|
||||
|
||||
// Context sets the context to be used in this call's Do method. Any
|
||||
// pending HTTP request will be aborted if the provided context is
|
||||
// canceled.
|
||||
func (c *GetCertForOpenIdConnectCall) Context(ctx context.Context) *GetCertForOpenIdConnectCall {
|
||||
c.ctx_ = ctx
|
||||
return c
|
||||
}
|
||||
|
||||
// Header returns an http.Header that can be modified by the caller to
|
||||
// add HTTP headers to the request.
|
||||
func (c *GetCertForOpenIdConnectCall) Header() http.Header {
|
||||
if c.header_ == nil {
|
||||
c.header_ = make(http.Header)
|
||||
}
|
||||
return c.header_
|
||||
}
|
||||
|
||||
func (c *GetCertForOpenIdConnectCall) doRequest(alt string) (*http.Response, error) {
|
||||
reqHeaders := make(http.Header)
|
||||
for k, v := range c.header_ {
|
||||
reqHeaders[k] = v
|
||||
}
|
||||
reqHeaders.Set("User-Agent", c.s.userAgent())
|
||||
if c.ifNoneMatch_ != "" {
|
||||
reqHeaders.Set("If-None-Match", c.ifNoneMatch_)
|
||||
}
|
||||
var body io.Reader = nil
|
||||
c.urlParams_.Set("alt", alt)
|
||||
urls := googleapi.ResolveRelative(c.s.BasePath, "oauth2/v2/certs")
|
||||
urls += "?" + c.urlParams_.Encode()
|
||||
req, _ := http.NewRequest("GET", urls, body)
|
||||
req.Header = reqHeaders
|
||||
return gensupport.SendRequest(c.ctx_, c.s.client, req)
|
||||
}
|
||||
|
||||
// Do executes the "oauth2.getCertForOpenIdConnect" call.
|
||||
// Exactly one of *Jwk or error will be non-nil. Any non-2xx status code
|
||||
// is an error. Response headers are in either
|
||||
// *Jwk.ServerResponse.Header or (if a response was returned at all) in
|
||||
// error.(*googleapi.Error).Header. Use googleapi.IsNotModified to check
|
||||
// whether the returned error was because http.StatusNotModified was
|
||||
// returned.
|
||||
func (c *GetCertForOpenIdConnectCall) Do(opts ...googleapi.CallOption) (*Jwk, error) {
|
||||
gensupport.SetOptions(c.urlParams_, opts...)
|
||||
res, err := c.doRequest("json")
|
||||
if res != nil && res.StatusCode == http.StatusNotModified {
|
||||
if res.Body != nil {
|
||||
res.Body.Close()
|
||||
}
|
||||
return nil, &googleapi.Error{
|
||||
Code: res.StatusCode,
|
||||
Header: res.Header,
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer googleapi.CloseBody(res)
|
||||
if err := googleapi.CheckResponse(res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := &Jwk{
|
||||
ServerResponse: googleapi.ServerResponse{
|
||||
Header: res.Header,
|
||||
HTTPStatusCode: res.StatusCode,
|
||||
},
|
||||
}
|
||||
target := &ret
|
||||
if err := json.NewDecoder(res.Body).Decode(target); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ret, nil
|
||||
// {
|
||||
// "httpMethod": "GET",
|
||||
// "id": "oauth2.getCertForOpenIdConnect",
|
||||
// "path": "oauth2/v2/certs",
|
||||
// "response": {
|
||||
// "$ref": "Jwk"
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
// method id "oauth2.tokeninfo":
|
||||
|
||||
type TokeninfoCall struct {
|
||||
s *Service
|
||||
urlParams_ gensupport.URLParams
|
||||
ctx_ context.Context
|
||||
header_ http.Header
|
||||
}
|
||||
|
||||
// Tokeninfo:
|
||||
func (s *Service) Tokeninfo() *TokeninfoCall {
|
||||
c := &TokeninfoCall{s: s, urlParams_: make(gensupport.URLParams)}
|
||||
return c
|
||||
}
|
||||
|
||||
// AccessToken sets the optional parameter "access_token":
|
||||
func (c *TokeninfoCall) AccessToken(accessToken string) *TokeninfoCall {
|
||||
c.urlParams_.Set("access_token", accessToken)
|
||||
return c
|
||||
}
|
||||
|
||||
// IdToken sets the optional parameter "id_token":
|
||||
func (c *TokeninfoCall) IdToken(idToken string) *TokeninfoCall {
|
||||
c.urlParams_.Set("id_token", idToken)
|
||||
return c
|
||||
}
|
||||
|
||||
// TokenHandle sets the optional parameter "token_handle":
|
||||
func (c *TokeninfoCall) TokenHandle(tokenHandle string) *TokeninfoCall {
|
||||
c.urlParams_.Set("token_handle", tokenHandle)
|
||||
return c
|
||||
}
|
||||
|
||||
// Fields allows partial responses to be retrieved. See
|
||||
// https://developers.google.com/gdata/docs/2.0/basics#PartialResponse
|
||||
// for more information.
|
||||
func (c *TokeninfoCall) Fields(s ...googleapi.Field) *TokeninfoCall {
|
||||
c.urlParams_.Set("fields", googleapi.CombineFields(s))
|
||||
return c
|
||||
}
|
||||
|
||||
// Context sets the context to be used in this call's Do method. Any
|
||||
// pending HTTP request will be aborted if the provided context is
|
||||
// canceled.
|
||||
func (c *TokeninfoCall) Context(ctx context.Context) *TokeninfoCall {
|
||||
c.ctx_ = ctx
|
||||
return c
|
||||
}
|
||||
|
||||
// Header returns an http.Header that can be modified by the caller to
|
||||
// add HTTP headers to the request.
|
||||
func (c *TokeninfoCall) Header() http.Header {
|
||||
if c.header_ == nil {
|
||||
c.header_ = make(http.Header)
|
||||
}
|
||||
return c.header_
|
||||
}
|
||||
|
||||
func (c *TokeninfoCall) doRequest(alt string) (*http.Response, error) {
|
||||
reqHeaders := make(http.Header)
|
||||
for k, v := range c.header_ {
|
||||
reqHeaders[k] = v
|
||||
}
|
||||
reqHeaders.Set("User-Agent", c.s.userAgent())
|
||||
var body io.Reader = nil
|
||||
c.urlParams_.Set("alt", alt)
|
||||
urls := googleapi.ResolveRelative(c.s.BasePath, "oauth2/v2/tokeninfo")
|
||||
urls += "?" + c.urlParams_.Encode()
|
||||
req, _ := http.NewRequest("POST", urls, body)
|
||||
req.Header = reqHeaders
|
||||
return gensupport.SendRequest(c.ctx_, c.s.client, req)
|
||||
}
|
||||
|
||||
// Do executes the "oauth2.tokeninfo" call.
|
||||
// Exactly one of *Tokeninfo or error will be non-nil. Any non-2xx
|
||||
// status code is an error. Response headers are in either
|
||||
// *Tokeninfo.ServerResponse.Header or (if a response was returned at
|
||||
// all) in error.(*googleapi.Error).Header. Use googleapi.IsNotModified
|
||||
// to check whether the returned error was because
|
||||
// http.StatusNotModified was returned.
|
||||
func (c *TokeninfoCall) Do(opts ...googleapi.CallOption) (*Tokeninfo, error) {
|
||||
gensupport.SetOptions(c.urlParams_, opts...)
|
||||
res, err := c.doRequest("json")
|
||||
if res != nil && res.StatusCode == http.StatusNotModified {
|
||||
if res.Body != nil {
|
||||
res.Body.Close()
|
||||
}
|
||||
return nil, &googleapi.Error{
|
||||
Code: res.StatusCode,
|
||||
Header: res.Header,
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer googleapi.CloseBody(res)
|
||||
if err := googleapi.CheckResponse(res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := &Tokeninfo{
|
||||
ServerResponse: googleapi.ServerResponse{
|
||||
Header: res.Header,
|
||||
HTTPStatusCode: res.StatusCode,
|
||||
},
|
||||
}
|
||||
target := &ret
|
||||
if err := json.NewDecoder(res.Body).Decode(target); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ret, nil
|
||||
// {
|
||||
// "httpMethod": "POST",
|
||||
// "id": "oauth2.tokeninfo",
|
||||
// "parameters": {
|
||||
// "access_token": {
|
||||
// "location": "query",
|
||||
// "type": "string"
|
||||
// },
|
||||
// "id_token": {
|
||||
// "location": "query",
|
||||
// "type": "string"
|
||||
// },
|
||||
// "token_handle": {
|
||||
// "location": "query",
|
||||
// "type": "string"
|
||||
// }
|
||||
// },
|
||||
// "path": "oauth2/v2/tokeninfo",
|
||||
// "response": {
|
||||
// "$ref": "Tokeninfo"
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
// method id "oauth2.userinfo.get":
|
||||
|
||||
type UserinfoGetCall struct {
|
||||
s *Service
|
||||
urlParams_ gensupport.URLParams
|
||||
ifNoneMatch_ string
|
||||
ctx_ context.Context
|
||||
header_ http.Header
|
||||
}
|
||||
|
||||
// Get:
|
||||
func (r *UserinfoService) Get() *UserinfoGetCall {
|
||||
c := &UserinfoGetCall{s: r.s, urlParams_: make(gensupport.URLParams)}
|
||||
return c
|
||||
}
|
||||
|
||||
// Fields allows partial responses to be retrieved. See
|
||||
// https://developers.google.com/gdata/docs/2.0/basics#PartialResponse
|
||||
// for more information.
|
||||
func (c *UserinfoGetCall) Fields(s ...googleapi.Field) *UserinfoGetCall {
|
||||
c.urlParams_.Set("fields", googleapi.CombineFields(s))
|
||||
return c
|
||||
}
|
||||
|
||||
// IfNoneMatch sets the optional parameter which makes the operation
|
||||
// fail if the object's ETag matches the given value. This is useful for
|
||||
// getting updates only after the object has changed since the last
|
||||
// request. Use googleapi.IsNotModified to check whether the response
|
||||
// error from Do is the result of In-None-Match.
|
||||
func (c *UserinfoGetCall) IfNoneMatch(entityTag string) *UserinfoGetCall {
|
||||
c.ifNoneMatch_ = entityTag
|
||||
return c
|
||||
}
|
||||
|
||||
// Context sets the context to be used in this call's Do method. Any
|
||||
// pending HTTP request will be aborted if the provided context is
|
||||
// canceled.
|
||||
func (c *UserinfoGetCall) Context(ctx context.Context) *UserinfoGetCall {
|
||||
c.ctx_ = ctx
|
||||
return c
|
||||
}
|
||||
|
||||
// Header returns an http.Header that can be modified by the caller to
|
||||
// add HTTP headers to the request.
|
||||
func (c *UserinfoGetCall) Header() http.Header {
|
||||
if c.header_ == nil {
|
||||
c.header_ = make(http.Header)
|
||||
}
|
||||
return c.header_
|
||||
}
|
||||
|
||||
func (c *UserinfoGetCall) doRequest(alt string) (*http.Response, error) {
|
||||
reqHeaders := make(http.Header)
|
||||
for k, v := range c.header_ {
|
||||
reqHeaders[k] = v
|
||||
}
|
||||
reqHeaders.Set("User-Agent", c.s.userAgent())
|
||||
if c.ifNoneMatch_ != "" {
|
||||
reqHeaders.Set("If-None-Match", c.ifNoneMatch_)
|
||||
}
|
||||
var body io.Reader = nil
|
||||
c.urlParams_.Set("alt", alt)
|
||||
urls := googleapi.ResolveRelative(c.s.BasePath, "oauth2/v2/userinfo")
|
||||
urls += "?" + c.urlParams_.Encode()
|
||||
req, _ := http.NewRequest("GET", urls, body)
|
||||
req.Header = reqHeaders
|
||||
return gensupport.SendRequest(c.ctx_, c.s.client, req)
|
||||
}
|
||||
|
||||
// Do executes the "oauth2.userinfo.get" call.
|
||||
// Exactly one of *Userinfoplus or error will be non-nil. Any non-2xx
|
||||
// status code is an error. Response headers are in either
|
||||
// *Userinfoplus.ServerResponse.Header or (if a response was returned at
|
||||
// all) in error.(*googleapi.Error).Header. Use googleapi.IsNotModified
|
||||
// to check whether the returned error was because
|
||||
// http.StatusNotModified was returned.
|
||||
func (c *UserinfoGetCall) Do(opts ...googleapi.CallOption) (*Userinfoplus, error) {
|
||||
gensupport.SetOptions(c.urlParams_, opts...)
|
||||
res, err := c.doRequest("json")
|
||||
if res != nil && res.StatusCode == http.StatusNotModified {
|
||||
if res.Body != nil {
|
||||
res.Body.Close()
|
||||
}
|
||||
return nil, &googleapi.Error{
|
||||
Code: res.StatusCode,
|
||||
Header: res.Header,
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer googleapi.CloseBody(res)
|
||||
if err := googleapi.CheckResponse(res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := &Userinfoplus{
|
||||
ServerResponse: googleapi.ServerResponse{
|
||||
Header: res.Header,
|
||||
HTTPStatusCode: res.StatusCode,
|
||||
},
|
||||
}
|
||||
target := &ret
|
||||
if err := json.NewDecoder(res.Body).Decode(target); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ret, nil
|
||||
// {
|
||||
// "httpMethod": "GET",
|
||||
// "id": "oauth2.userinfo.get",
|
||||
// "path": "oauth2/v2/userinfo",
|
||||
// "response": {
|
||||
// "$ref": "Userinfoplus"
|
||||
// },
|
||||
// "scopes": [
|
||||
// "https://www.googleapis.com/auth/plus.login",
|
||||
// "https://www.googleapis.com/auth/plus.me",
|
||||
// "https://www.googleapis.com/auth/userinfo.email",
|
||||
// "https://www.googleapis.com/auth/userinfo.profile"
|
||||
// ]
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
// method id "oauth2.userinfo.v2.me.get":
|
||||
|
||||
type UserinfoV2MeGetCall struct {
|
||||
s *Service
|
||||
urlParams_ gensupport.URLParams
|
||||
ifNoneMatch_ string
|
||||
ctx_ context.Context
|
||||
header_ http.Header
|
||||
}
|
||||
|
||||
// Get:
|
||||
func (r *UserinfoV2MeService) Get() *UserinfoV2MeGetCall {
|
||||
c := &UserinfoV2MeGetCall{s: r.s, urlParams_: make(gensupport.URLParams)}
|
||||
return c
|
||||
}
|
||||
|
||||
// Fields allows partial responses to be retrieved. See
|
||||
// https://developers.google.com/gdata/docs/2.0/basics#PartialResponse
|
||||
// for more information.
|
||||
func (c *UserinfoV2MeGetCall) Fields(s ...googleapi.Field) *UserinfoV2MeGetCall {
|
||||
c.urlParams_.Set("fields", googleapi.CombineFields(s))
|
||||
return c
|
||||
}
|
||||
|
||||
// IfNoneMatch sets the optional parameter which makes the operation
|
||||
// fail if the object's ETag matches the given value. This is useful for
|
||||
// getting updates only after the object has changed since the last
|
||||
// request. Use googleapi.IsNotModified to check whether the response
|
||||
// error from Do is the result of In-None-Match.
|
||||
func (c *UserinfoV2MeGetCall) IfNoneMatch(entityTag string) *UserinfoV2MeGetCall {
|
||||
c.ifNoneMatch_ = entityTag
|
||||
return c
|
||||
}
|
||||
|
||||
// Context sets the context to be used in this call's Do method. Any
|
||||
// pending HTTP request will be aborted if the provided context is
|
||||
// canceled.
|
||||
func (c *UserinfoV2MeGetCall) Context(ctx context.Context) *UserinfoV2MeGetCall {
|
||||
c.ctx_ = ctx
|
||||
return c
|
||||
}
|
||||
|
||||
// Header returns an http.Header that can be modified by the caller to
|
||||
// add HTTP headers to the request.
|
||||
func (c *UserinfoV2MeGetCall) Header() http.Header {
|
||||
if c.header_ == nil {
|
||||
c.header_ = make(http.Header)
|
||||
}
|
||||
return c.header_
|
||||
}
|
||||
|
||||
func (c *UserinfoV2MeGetCall) doRequest(alt string) (*http.Response, error) {
|
||||
reqHeaders := make(http.Header)
|
||||
for k, v := range c.header_ {
|
||||
reqHeaders[k] = v
|
||||
}
|
||||
reqHeaders.Set("User-Agent", c.s.userAgent())
|
||||
if c.ifNoneMatch_ != "" {
|
||||
reqHeaders.Set("If-None-Match", c.ifNoneMatch_)
|
||||
}
|
||||
var body io.Reader = nil
|
||||
c.urlParams_.Set("alt", alt)
|
||||
urls := googleapi.ResolveRelative(c.s.BasePath, "userinfo/v2/me")
|
||||
urls += "?" + c.urlParams_.Encode()
|
||||
req, _ := http.NewRequest("GET", urls, body)
|
||||
req.Header = reqHeaders
|
||||
return gensupport.SendRequest(c.ctx_, c.s.client, req)
|
||||
}
|
||||
|
||||
// Do executes the "oauth2.userinfo.v2.me.get" call.
|
||||
// Exactly one of *Userinfoplus or error will be non-nil. Any non-2xx
|
||||
// status code is an error. Response headers are in either
|
||||
// *Userinfoplus.ServerResponse.Header or (if a response was returned at
|
||||
// all) in error.(*googleapi.Error).Header. Use googleapi.IsNotModified
|
||||
// to check whether the returned error was because
|
||||
// http.StatusNotModified was returned.
|
||||
func (c *UserinfoV2MeGetCall) Do(opts ...googleapi.CallOption) (*Userinfoplus, error) {
|
||||
gensupport.SetOptions(c.urlParams_, opts...)
|
||||
res, err := c.doRequest("json")
|
||||
if res != nil && res.StatusCode == http.StatusNotModified {
|
||||
if res.Body != nil {
|
||||
res.Body.Close()
|
||||
}
|
||||
return nil, &googleapi.Error{
|
||||
Code: res.StatusCode,
|
||||
Header: res.Header,
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer googleapi.CloseBody(res)
|
||||
if err := googleapi.CheckResponse(res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := &Userinfoplus{
|
||||
ServerResponse: googleapi.ServerResponse{
|
||||
Header: res.Header,
|
||||
HTTPStatusCode: res.StatusCode,
|
||||
},
|
||||
}
|
||||
target := &ret
|
||||
if err := json.NewDecoder(res.Body).Decode(target); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ret, nil
|
||||
// {
|
||||
// "httpMethod": "GET",
|
||||
// "id": "oauth2.userinfo.v2.me.get",
|
||||
// "path": "userinfo/v2/me",
|
||||
// "response": {
|
||||
// "$ref": "Userinfoplus"
|
||||
// },
|
||||
// "scopes": [
|
||||
// "https://www.googleapis.com/auth/plus.login",
|
||||
// "https://www.googleapis.com/auth/plus.me",
|
||||
// "https://www.googleapis.com/auth/userinfo.email",
|
||||
// "https://www.googleapis.com/auth/userinfo.profile"
|
||||
// ]
|
||||
// }
|
||||
|
||||
}
|
||||
10
vendor/gopkg.in/square/go-jose.v2/BUG-BOUNTY.md
generated
vendored
Normal file
10
vendor/gopkg.in/square/go-jose.v2/BUG-BOUNTY.md
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
Serious about security
|
||||
======================
|
||||
|
||||
Square recognizes the important contributions the security research community
|
||||
can make. We therefore encourage reporting security issues with the code
|
||||
contained in this repository.
|
||||
|
||||
If you believe you have discovered a security vulnerability, please follow the
|
||||
guidelines at <https://hackerone.com/square-open-source>.
|
||||
|
||||
14
vendor/gopkg.in/square/go-jose.v2/CONTRIBUTING.md
generated
vendored
Normal file
14
vendor/gopkg.in/square/go-jose.v2/CONTRIBUTING.md
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Contributing
|
||||
|
||||
If you would like to contribute code to go-jose you can do so through GitHub by
|
||||
forking the repository and sending a pull request.
|
||||
|
||||
When submitting code, please make every effort to follow existing conventions
|
||||
and style in order to keep the code as readable as possible. Please also make
|
||||
sure all tests pass by running `go test`, and format your code with `go fmt`.
|
||||
We also recommend using `golint` and `errcheck`.
|
||||
|
||||
Before your code can be accepted into the project you must also sign the
|
||||
[Individual Contributor License Agreement][1].
|
||||
|
||||
[1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
|
||||
202
vendor/gopkg.in/square/go-jose.v2/LICENSE
generated
vendored
Normal file
202
vendor/gopkg.in/square/go-jose.v2/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
119
vendor/gopkg.in/square/go-jose.v2/README.md
generated
vendored
Normal file
119
vendor/gopkg.in/square/go-jose.v2/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# Go JOSE
|
||||
|
||||
[](https://godoc.org/gopkg.in/square/go-jose.v1)
|
||||
[](https://godoc.org/gopkg.in/square/go-jose.v2)
|
||||
[](https://raw.githubusercontent.com/square/go-jose/master/LICENSE)
|
||||
[](https://travis-ci.org/square/go-jose)
|
||||
[](https://coveralls.io/r/square/go-jose)
|
||||
|
||||
Package jose aims to provide an implementation of the Javascript Object Signing
|
||||
and Encryption set of standards. This includes support for JSON Web Encryption,
|
||||
JSON Web Signature, and JSON Web Token standards.
|
||||
|
||||
**Disclaimer**: This library contains encryption software that is subject to
|
||||
the U.S. Export Administration Regulations. You may not export, re-export,
|
||||
transfer or download this code or any part of it in violation of any United
|
||||
States law, directive or regulation. In particular this software may not be
|
||||
exported or re-exported in any form or on any media to Iran, North Sudan,
|
||||
Syria, Cuba, or North Korea, or to denied persons or entities mentioned on any
|
||||
US maintained blocked list.
|
||||
|
||||
## Overview
|
||||
|
||||
The implementation follows the
|
||||
[JSON Web Encryption](http://dx.doi.org/10.17487/RFC7516) (RFC 7516),
|
||||
[JSON Web Signature](http://dx.doi.org/10.17487/RFC7515) (RFC 7515), and
|
||||
[JSON Web Token](http://dx.doi.org/10.17487/RFC7519) (RFC 7519).
|
||||
Tables of supported algorithms are shown below. The library supports both
|
||||
the compact and full serialization formats, and has optional support for
|
||||
multiple recipients. It also comes with a small command-line utility
|
||||
([`jose-util`](https://github.com/square/go-jose/tree/v2/jose-util))
|
||||
for dealing with JOSE messages in a shell.
|
||||
|
||||
**Note**: We use a forked version of the `encoding/json` package from the Go
|
||||
standard library which uses case-sensitive matching for member names (instead
|
||||
of [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html)).
|
||||
This is to avoid differences in interpretation of messages between go-jose and
|
||||
libraries in other languages.
|
||||
|
||||
### Versions
|
||||
|
||||
We use [gopkg.in](https://gopkg.in) for versioning.
|
||||
|
||||
[Version 1](https://gopkg.in/square/go-jose.v1) is the old stable version:
|
||||
|
||||
import "gopkg.in/square/go-jose.v1"
|
||||
|
||||
[Version 2](https://gopkg.in/square/go-jose.v2) is for new development:
|
||||
|
||||
import "gopkg.in/square/go-jose.v2"
|
||||
|
||||
The interface for [go-jose.v1](https://gopkg.in/square/go-jose.v1) will remain
|
||||
backwards compatible. No new feature development will take place on the `v1` branch,
|
||||
however bug fixes and security fixes will be backported.
|
||||
|
||||
The interface for [go-jose.v2](https://gopkg.in/square/go-jose.v2) is mostly
|
||||
stable, but we suggest pinning to a particular revision for now as we still reserve
|
||||
the right to make changes. New feature development happens on this branch.
|
||||
|
||||
New in [go-jose.v2](https://gopkg.in/square/go-jose.v2) is a
|
||||
[jwt](https://godoc.org/gopkg.in/square/go-jose.v2/jwt) sub-package
|
||||
contributed by [@shaxbee](https://github.com/shaxbee).
|
||||
|
||||
### Supported algorithms
|
||||
|
||||
See below for a table of supported algorithms. Algorithm identifiers match
|
||||
the names in the [JSON Web Algorithms](http://dx.doi.org/10.17487/RFC7518)
|
||||
standard where possible. The Godoc reference has a list of constants.
|
||||
|
||||
Key encryption | Algorithm identifier(s)
|
||||
:------------------------- | :------------------------------
|
||||
RSA-PKCS#1v1.5 | RSA1_5
|
||||
RSA-OAEP | RSA-OAEP, RSA-OAEP-256
|
||||
AES key wrap | A128KW, A192KW, A256KW
|
||||
AES-GCM key wrap | A128GCMKW, A192GCMKW, A256GCMKW
|
||||
ECDH-ES + AES key wrap | ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW
|
||||
ECDH-ES (direct) | ECDH-ES<sup>1</sup>
|
||||
Direct encryption | dir<sup>1</sup>
|
||||
|
||||
<sup>1. Not supported in multi-recipient mode</sup>
|
||||
|
||||
Signing / MAC | Algorithm identifier(s)
|
||||
:------------------------- | :------------------------------
|
||||
RSASSA-PKCS#1v1.5 | RS256, RS384, RS512
|
||||
RSASSA-PSS | PS256, PS384, PS512
|
||||
HMAC | HS256, HS384, HS512
|
||||
ECDSA | ES256, ES384, ES512
|
||||
|
||||
Content encryption | Algorithm identifier(s)
|
||||
:------------------------- | :------------------------------
|
||||
AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512
|
||||
AES-GCM | A128GCM, A192GCM, A256GCM
|
||||
|
||||
Compression | Algorithm identifiers(s)
|
||||
:------------------------- | -------------------------------
|
||||
DEFLATE (RFC 1951) | DEF
|
||||
|
||||
### Supported key types
|
||||
|
||||
See below for a table of supported key types. These are understood by the
|
||||
library, and can be passed to corresponding functions such as `NewEncrypter` or
|
||||
`NewSigner`. Each of these keys can also be wrapped in a JWK if desired, which
|
||||
allows attaching a key id.
|
||||
|
||||
Algorithm(s) | Corresponding types
|
||||
:------------------------- | -------------------------------
|
||||
RSA | *[rsa.PublicKey](http://golang.org/pkg/crypto/rsa/#PublicKey), *[rsa.PrivateKey](http://golang.org/pkg/crypto/rsa/#PrivateKey)
|
||||
ECDH, ECDSA | *[ecdsa.PublicKey](http://golang.org/pkg/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](http://golang.org/pkg/crypto/ecdsa/#PrivateKey)
|
||||
AES, HMAC | []byte
|
||||
|
||||
## Examples
|
||||
|
||||
[](https://godoc.org/gopkg.in/square/go-jose.v1)
|
||||
[](https://godoc.org/gopkg.in/square/go-jose.v2)
|
||||
|
||||
Examples can be found in the Godoc
|
||||
reference for this package. The
|
||||
[`jose-util`](https://github.com/square/go-jose/tree/v2/jose-util)
|
||||
subdirectory also contains a small command-line utility which might be useful
|
||||
as an example.
|
||||
591
vendor/gopkg.in/square/go-jose.v2/asymmetric.go
generated
vendored
Normal file
591
vendor/gopkg.in/square/go-jose.v2/asymmetric.go
generated
vendored
Normal file
|
|
@ -0,0 +1,591 @@
|
|||
/*-
|
||||
* Copyright 2014 Square Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jose
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/aes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"gopkg.in/square/go-jose.v2/cipher"
|
||||
"gopkg.in/square/go-jose.v2/json"
|
||||
)
|
||||
|
||||
// A generic RSA-based encrypter/verifier
|
||||
type rsaEncrypterVerifier struct {
|
||||
publicKey *rsa.PublicKey
|
||||
}
|
||||
|
||||
// A generic RSA-based decrypter/signer
|
||||
type rsaDecrypterSigner struct {
|
||||
privateKey *rsa.PrivateKey
|
||||
}
|
||||
|
||||
// A generic EC-based encrypter/verifier
|
||||
type ecEncrypterVerifier struct {
|
||||
publicKey *ecdsa.PublicKey
|
||||
}
|
||||
|
||||
type edEncrypterVerifier struct {
|
||||
publicKey ed25519.PublicKey
|
||||
}
|
||||
|
||||
// A key generator for ECDH-ES
|
||||
type ecKeyGenerator struct {
|
||||
size int
|
||||
algID string
|
||||
publicKey *ecdsa.PublicKey
|
||||
}
|
||||
|
||||
// A generic EC-based decrypter/signer
|
||||
type ecDecrypterSigner struct {
|
||||
privateKey *ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
type edDecrypterSigner struct {
|
||||
privateKey ed25519.PrivateKey
|
||||
}
|
||||
|
||||
// newRSARecipient creates recipientKeyInfo based on the given key.
|
||||
func newRSARecipient(keyAlg KeyAlgorithm, publicKey *rsa.PublicKey) (recipientKeyInfo, error) {
|
||||
// Verify that key management algorithm is supported by this encrypter
|
||||
switch keyAlg {
|
||||
case RSA1_5, RSA_OAEP, RSA_OAEP_256:
|
||||
default:
|
||||
return recipientKeyInfo{}, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
if publicKey == nil {
|
||||
return recipientKeyInfo{}, errors.New("invalid public key")
|
||||
}
|
||||
|
||||
return recipientKeyInfo{
|
||||
keyAlg: keyAlg,
|
||||
keyEncrypter: &rsaEncrypterVerifier{
|
||||
publicKey: publicKey,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newRSASigner creates a recipientSigInfo based on the given key.
|
||||
func newRSASigner(sigAlg SignatureAlgorithm, privateKey *rsa.PrivateKey) (recipientSigInfo, error) {
|
||||
// Verify that key management algorithm is supported by this encrypter
|
||||
switch sigAlg {
|
||||
case RS256, RS384, RS512, PS256, PS384, PS512:
|
||||
default:
|
||||
return recipientSigInfo{}, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
if privateKey == nil {
|
||||
return recipientSigInfo{}, errors.New("invalid private key")
|
||||
}
|
||||
|
||||
return recipientSigInfo{
|
||||
sigAlg: sigAlg,
|
||||
publicKey: &JSONWebKey{
|
||||
Key: &privateKey.PublicKey,
|
||||
},
|
||||
signer: &rsaDecrypterSigner{
|
||||
privateKey: privateKey,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newEd25519Signer(sigAlg SignatureAlgorithm, privateKey ed25519.PrivateKey) (recipientSigInfo, error) {
|
||||
if sigAlg != EdDSA {
|
||||
return recipientSigInfo{}, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
if privateKey == nil {
|
||||
return recipientSigInfo{}, errors.New("invalid private key")
|
||||
}
|
||||
return recipientSigInfo{
|
||||
sigAlg: sigAlg,
|
||||
publicKey: &JSONWebKey{
|
||||
Key: privateKey.Public(),
|
||||
},
|
||||
signer: &edDecrypterSigner{
|
||||
privateKey: privateKey,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newECDHRecipient creates recipientKeyInfo based on the given key.
|
||||
func newECDHRecipient(keyAlg KeyAlgorithm, publicKey *ecdsa.PublicKey) (recipientKeyInfo, error) {
|
||||
// Verify that key management algorithm is supported by this encrypter
|
||||
switch keyAlg {
|
||||
case ECDH_ES, ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW:
|
||||
default:
|
||||
return recipientKeyInfo{}, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
if publicKey == nil || !publicKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) {
|
||||
return recipientKeyInfo{}, errors.New("invalid public key")
|
||||
}
|
||||
|
||||
return recipientKeyInfo{
|
||||
keyAlg: keyAlg,
|
||||
keyEncrypter: &ecEncrypterVerifier{
|
||||
publicKey: publicKey,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newECDSASigner creates a recipientSigInfo based on the given key.
|
||||
func newECDSASigner(sigAlg SignatureAlgorithm, privateKey *ecdsa.PrivateKey) (recipientSigInfo, error) {
|
||||
// Verify that key management algorithm is supported by this encrypter
|
||||
switch sigAlg {
|
||||
case ES256, ES384, ES512:
|
||||
default:
|
||||
return recipientSigInfo{}, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
if privateKey == nil {
|
||||
return recipientSigInfo{}, errors.New("invalid private key")
|
||||
}
|
||||
|
||||
return recipientSigInfo{
|
||||
sigAlg: sigAlg,
|
||||
publicKey: &JSONWebKey{
|
||||
Key: &privateKey.PublicKey,
|
||||
},
|
||||
signer: &ecDecrypterSigner{
|
||||
privateKey: privateKey,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Encrypt the given payload and update the object.
|
||||
func (ctx rsaEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
|
||||
encryptedKey, err := ctx.encrypt(cek, alg)
|
||||
if err != nil {
|
||||
return recipientInfo{}, err
|
||||
}
|
||||
|
||||
return recipientInfo{
|
||||
encryptedKey: encryptedKey,
|
||||
header: &rawHeader{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Encrypt the given payload. Based on the key encryption algorithm,
|
||||
// this will either use RSA-PKCS1v1.5 or RSA-OAEP (with SHA-1 or SHA-256).
|
||||
func (ctx rsaEncrypterVerifier) encrypt(cek []byte, alg KeyAlgorithm) ([]byte, error) {
|
||||
switch alg {
|
||||
case RSA1_5:
|
||||
return rsa.EncryptPKCS1v15(randReader, ctx.publicKey, cek)
|
||||
case RSA_OAEP:
|
||||
return rsa.EncryptOAEP(sha1.New(), randReader, ctx.publicKey, cek, []byte{})
|
||||
case RSA_OAEP_256:
|
||||
return rsa.EncryptOAEP(sha256.New(), randReader, ctx.publicKey, cek, []byte{})
|
||||
}
|
||||
|
||||
return nil, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
// Decrypt the given payload and return the content encryption key.
|
||||
func (ctx rsaDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
|
||||
return ctx.decrypt(recipient.encryptedKey, headers.getAlgorithm(), generator)
|
||||
}
|
||||
|
||||
// Decrypt the given payload. Based on the key encryption algorithm,
|
||||
// this will either use RSA-PKCS1v1.5 or RSA-OAEP (with SHA-1 or SHA-256).
|
||||
func (ctx rsaDecrypterSigner) decrypt(jek []byte, alg KeyAlgorithm, generator keyGenerator) ([]byte, error) {
|
||||
// Note: The random reader on decrypt operations is only used for blinding,
|
||||
// so stubbing is meanlingless (hence the direct use of rand.Reader).
|
||||
switch alg {
|
||||
case RSA1_5:
|
||||
defer func() {
|
||||
// DecryptPKCS1v15SessionKey sometimes panics on an invalid payload
|
||||
// because of an index out of bounds error, which we want to ignore.
|
||||
// This has been fixed in Go 1.3.1 (released 2014/08/13), the recover()
|
||||
// only exists for preventing crashes with unpatched versions.
|
||||
// See: https://groups.google.com/forum/#!topic/golang-dev/7ihX6Y6kx9k
|
||||
// See: https://code.google.com/p/go/source/detail?r=58ee390ff31602edb66af41ed10901ec95904d33
|
||||
_ = recover()
|
||||
}()
|
||||
|
||||
// Perform some input validation.
|
||||
keyBytes := ctx.privateKey.PublicKey.N.BitLen() / 8
|
||||
if keyBytes != len(jek) {
|
||||
// Input size is incorrect, the encrypted payload should always match
|
||||
// the size of the public modulus (e.g. using a 2048 bit key will
|
||||
// produce 256 bytes of output). Reject this since it's invalid input.
|
||||
return nil, ErrCryptoFailure
|
||||
}
|
||||
|
||||
cek, _, err := generator.genKey()
|
||||
if err != nil {
|
||||
return nil, ErrCryptoFailure
|
||||
}
|
||||
|
||||
// When decrypting an RSA-PKCS1v1.5 payload, we must take precautions to
|
||||
// prevent chosen-ciphertext attacks as described in RFC 3218, "Preventing
|
||||
// the Million Message Attack on Cryptographic Message Syntax". We are
|
||||
// therefore deliberately ignoring errors here.
|
||||
_ = rsa.DecryptPKCS1v15SessionKey(rand.Reader, ctx.privateKey, jek, cek)
|
||||
|
||||
return cek, nil
|
||||
case RSA_OAEP:
|
||||
// Use rand.Reader for RSA blinding
|
||||
return rsa.DecryptOAEP(sha1.New(), rand.Reader, ctx.privateKey, jek, []byte{})
|
||||
case RSA_OAEP_256:
|
||||
// Use rand.Reader for RSA blinding
|
||||
return rsa.DecryptOAEP(sha256.New(), rand.Reader, ctx.privateKey, jek, []byte{})
|
||||
}
|
||||
|
||||
return nil, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
// Sign the given payload
|
||||
func (ctx rsaDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
||||
var hash crypto.Hash
|
||||
|
||||
switch alg {
|
||||
case RS256, PS256:
|
||||
hash = crypto.SHA256
|
||||
case RS384, PS384:
|
||||
hash = crypto.SHA384
|
||||
case RS512, PS512:
|
||||
hash = crypto.SHA512
|
||||
default:
|
||||
return Signature{}, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
hasher := hash.New()
|
||||
|
||||
// According to documentation, Write() on hash never fails
|
||||
_, _ = hasher.Write(payload)
|
||||
hashed := hasher.Sum(nil)
|
||||
|
||||
var out []byte
|
||||
var err error
|
||||
|
||||
switch alg {
|
||||
case RS256, RS384, RS512:
|
||||
out, err = rsa.SignPKCS1v15(randReader, ctx.privateKey, hash, hashed)
|
||||
case PS256, PS384, PS512:
|
||||
out, err = rsa.SignPSS(randReader, ctx.privateKey, hash, hashed, &rsa.PSSOptions{
|
||||
SaltLength: rsa.PSSSaltLengthAuto,
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return Signature{}, err
|
||||
}
|
||||
|
||||
return Signature{
|
||||
Signature: out,
|
||||
protected: &rawHeader{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Verify the given payload
|
||||
func (ctx rsaEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
|
||||
var hash crypto.Hash
|
||||
|
||||
switch alg {
|
||||
case RS256, PS256:
|
||||
hash = crypto.SHA256
|
||||
case RS384, PS384:
|
||||
hash = crypto.SHA384
|
||||
case RS512, PS512:
|
||||
hash = crypto.SHA512
|
||||
default:
|
||||
return ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
hasher := hash.New()
|
||||
|
||||
// According to documentation, Write() on hash never fails
|
||||
_, _ = hasher.Write(payload)
|
||||
hashed := hasher.Sum(nil)
|
||||
|
||||
switch alg {
|
||||
case RS256, RS384, RS512:
|
||||
return rsa.VerifyPKCS1v15(ctx.publicKey, hash, hashed, signature)
|
||||
case PS256, PS384, PS512:
|
||||
return rsa.VerifyPSS(ctx.publicKey, hash, hashed, signature, nil)
|
||||
}
|
||||
|
||||
return ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
// Encrypt the given payload and update the object.
|
||||
func (ctx ecEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
|
||||
switch alg {
|
||||
case ECDH_ES:
|
||||
// ECDH-ES mode doesn't wrap a key, the shared secret is used directly as the key.
|
||||
return recipientInfo{
|
||||
header: &rawHeader{},
|
||||
}, nil
|
||||
case ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW:
|
||||
default:
|
||||
return recipientInfo{}, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
generator := ecKeyGenerator{
|
||||
algID: string(alg),
|
||||
publicKey: ctx.publicKey,
|
||||
}
|
||||
|
||||
switch alg {
|
||||
case ECDH_ES_A128KW:
|
||||
generator.size = 16
|
||||
case ECDH_ES_A192KW:
|
||||
generator.size = 24
|
||||
case ECDH_ES_A256KW:
|
||||
generator.size = 32
|
||||
}
|
||||
|
||||
kek, header, err := generator.genKey()
|
||||
if err != nil {
|
||||
return recipientInfo{}, err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(kek)
|
||||
if err != nil {
|
||||
return recipientInfo{}, err
|
||||
}
|
||||
|
||||
jek, err := josecipher.KeyWrap(block, cek)
|
||||
if err != nil {
|
||||
return recipientInfo{}, err
|
||||
}
|
||||
|
||||
return recipientInfo{
|
||||
encryptedKey: jek,
|
||||
header: &header,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Get key size for EC key generator
|
||||
func (ctx ecKeyGenerator) keySize() int {
|
||||
return ctx.size
|
||||
}
|
||||
|
||||
// Get a content encryption key for ECDH-ES
|
||||
func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) {
|
||||
priv, err := ecdsa.GenerateKey(ctx.publicKey.Curve, randReader)
|
||||
if err != nil {
|
||||
return nil, rawHeader{}, err
|
||||
}
|
||||
|
||||
out := josecipher.DeriveECDHES(ctx.algID, []byte{}, []byte{}, priv, ctx.publicKey, ctx.size)
|
||||
|
||||
b, err := json.Marshal(&JSONWebKey{
|
||||
Key: &priv.PublicKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
headers := rawHeader{
|
||||
headerEPK: makeRawMessage(b),
|
||||
}
|
||||
|
||||
return out, headers, nil
|
||||
}
|
||||
|
||||
// Decrypt the given payload and return the content encryption key.
|
||||
func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
|
||||
epk, err := headers.getEPK()
|
||||
if err != nil {
|
||||
return nil, errors.New("square/go-jose: invalid epk header")
|
||||
}
|
||||
if epk == nil {
|
||||
return nil, errors.New("square/go-jose: missing epk header")
|
||||
}
|
||||
|
||||
publicKey, ok := epk.Key.(*ecdsa.PublicKey)
|
||||
if publicKey == nil || !ok {
|
||||
return nil, errors.New("square/go-jose: invalid epk header")
|
||||
}
|
||||
|
||||
if !ctx.privateKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) {
|
||||
return nil, errors.New("square/go-jose: invalid public key in epk header")
|
||||
}
|
||||
|
||||
apuData, err := headers.getAPU()
|
||||
if err != nil {
|
||||
return nil, errors.New("square/go-jose: invalid apu header")
|
||||
}
|
||||
apvData, err := headers.getAPV()
|
||||
if err != nil {
|
||||
return nil, errors.New("square/go-jose: invalid apv header")
|
||||
}
|
||||
|
||||
deriveKey := func(algID string, size int) []byte {
|
||||
return josecipher.DeriveECDHES(algID, apuData.bytes(), apvData.bytes(), ctx.privateKey, publicKey, size)
|
||||
}
|
||||
|
||||
var keySize int
|
||||
|
||||
algorithm := headers.getAlgorithm()
|
||||
switch algorithm {
|
||||
case ECDH_ES:
|
||||
// ECDH-ES uses direct key agreement, no key unwrapping necessary.
|
||||
return deriveKey(string(headers.getEncryption()), generator.keySize()), nil
|
||||
case ECDH_ES_A128KW:
|
||||
keySize = 16
|
||||
case ECDH_ES_A192KW:
|
||||
keySize = 24
|
||||
case ECDH_ES_A256KW:
|
||||
keySize = 32
|
||||
default:
|
||||
return nil, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
key := deriveKey(string(algorithm), keySize)
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return josecipher.KeyUnwrap(block, recipient.encryptedKey)
|
||||
}
|
||||
func (ctx edDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
||||
if alg != EdDSA {
|
||||
return Signature{}, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
sig, err := ctx.privateKey.Sign(randReader, payload, crypto.Hash(0))
|
||||
if err != nil {
|
||||
return Signature{}, err
|
||||
}
|
||||
|
||||
return Signature{
|
||||
Signature: sig,
|
||||
protected: &rawHeader{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ctx edEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
|
||||
if alg != EdDSA {
|
||||
return ErrUnsupportedAlgorithm
|
||||
}
|
||||
ok := ed25519.Verify(ctx.publicKey, payload, signature)
|
||||
if !ok {
|
||||
return errors.New("square/go-jose: ed25519 signature failed to verify")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign the given payload
|
||||
func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
||||
var expectedBitSize int
|
||||
var hash crypto.Hash
|
||||
|
||||
switch alg {
|
||||
case ES256:
|
||||
expectedBitSize = 256
|
||||
hash = crypto.SHA256
|
||||
case ES384:
|
||||
expectedBitSize = 384
|
||||
hash = crypto.SHA384
|
||||
case ES512:
|
||||
expectedBitSize = 521
|
||||
hash = crypto.SHA512
|
||||
}
|
||||
|
||||
curveBits := ctx.privateKey.Curve.Params().BitSize
|
||||
if expectedBitSize != curveBits {
|
||||
return Signature{}, fmt.Errorf("square/go-jose: expected %d bit key, got %d bits instead", expectedBitSize, curveBits)
|
||||
}
|
||||
|
||||
hasher := hash.New()
|
||||
|
||||
// According to documentation, Write() on hash never fails
|
||||
_, _ = hasher.Write(payload)
|
||||
hashed := hasher.Sum(nil)
|
||||
|
||||
r, s, err := ecdsa.Sign(randReader, ctx.privateKey, hashed)
|
||||
if err != nil {
|
||||
return Signature{}, err
|
||||
}
|
||||
|
||||
keyBytes := curveBits / 8
|
||||
if curveBits%8 > 0 {
|
||||
keyBytes++
|
||||
}
|
||||
|
||||
// We serialize the outpus (r and s) into big-endian byte arrays and pad
|
||||
// them with zeros on the left to make sure the sizes work out. Both arrays
|
||||
// must be keyBytes long, and the output must be 2*keyBytes long.
|
||||
rBytes := r.Bytes()
|
||||
rBytesPadded := make([]byte, keyBytes)
|
||||
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
|
||||
|
||||
sBytes := s.Bytes()
|
||||
sBytesPadded := make([]byte, keyBytes)
|
||||
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
|
||||
|
||||
out := append(rBytesPadded, sBytesPadded...)
|
||||
|
||||
return Signature{
|
||||
Signature: out,
|
||||
protected: &rawHeader{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Verify the given payload
|
||||
func (ctx ecEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
|
||||
var keySize int
|
||||
var hash crypto.Hash
|
||||
|
||||
switch alg {
|
||||
case ES256:
|
||||
keySize = 32
|
||||
hash = crypto.SHA256
|
||||
case ES384:
|
||||
keySize = 48
|
||||
hash = crypto.SHA384
|
||||
case ES512:
|
||||
keySize = 66
|
||||
hash = crypto.SHA512
|
||||
default:
|
||||
return ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
if len(signature) != 2*keySize {
|
||||
return fmt.Errorf("square/go-jose: invalid signature size, have %d bytes, wanted %d", len(signature), 2*keySize)
|
||||
}
|
||||
|
||||
hasher := hash.New()
|
||||
|
||||
// According to documentation, Write() on hash never fails
|
||||
_, _ = hasher.Write(payload)
|
||||
hashed := hasher.Sum(nil)
|
||||
|
||||
r := big.NewInt(0).SetBytes(signature[:keySize])
|
||||
s := big.NewInt(0).SetBytes(signature[keySize:])
|
||||
|
||||
match := ecdsa.Verify(ctx.publicKey, hashed, r, s)
|
||||
if !match {
|
||||
return errors.New("square/go-jose: ecdsa signature failed to verify")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
196
vendor/gopkg.in/square/go-jose.v2/cipher/cbc_hmac.go
generated
vendored
Normal file
196
vendor/gopkg.in/square/go-jose.v2/cipher/cbc_hmac.go
generated
vendored
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
/*-
|
||||
* Copyright 2014 Square Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package josecipher
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash"
|
||||
)
|
||||
|
||||
const (
|
||||
nonceBytes = 16
|
||||
)
|
||||
|
||||
// NewCBCHMAC instantiates a new AEAD based on CBC+HMAC.
|
||||
func NewCBCHMAC(key []byte, newBlockCipher func([]byte) (cipher.Block, error)) (cipher.AEAD, error) {
|
||||
keySize := len(key) / 2
|
||||
integrityKey := key[:keySize]
|
||||
encryptionKey := key[keySize:]
|
||||
|
||||
blockCipher, err := newBlockCipher(encryptionKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var hash func() hash.Hash
|
||||
switch keySize {
|
||||
case 16:
|
||||
hash = sha256.New
|
||||
case 24:
|
||||
hash = sha512.New384
|
||||
case 32:
|
||||
hash = sha512.New
|
||||
}
|
||||
|
||||
return &cbcAEAD{
|
||||
hash: hash,
|
||||
blockCipher: blockCipher,
|
||||
authtagBytes: keySize,
|
||||
integrityKey: integrityKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// An AEAD based on CBC+HMAC
|
||||
type cbcAEAD struct {
|
||||
hash func() hash.Hash
|
||||
authtagBytes int
|
||||
integrityKey []byte
|
||||
blockCipher cipher.Block
|
||||
}
|
||||
|
||||
func (ctx *cbcAEAD) NonceSize() int {
|
||||
return nonceBytes
|
||||
}
|
||||
|
||||
func (ctx *cbcAEAD) Overhead() int {
|
||||
// Maximum overhead is block size (for padding) plus auth tag length, where
|
||||
// the length of the auth tag is equivalent to the key size.
|
||||
return ctx.blockCipher.BlockSize() + ctx.authtagBytes
|
||||
}
|
||||
|
||||
// Seal encrypts and authenticates the plaintext.
|
||||
func (ctx *cbcAEAD) Seal(dst, nonce, plaintext, data []byte) []byte {
|
||||
// Output buffer -- must take care not to mangle plaintext input.
|
||||
ciphertext := make([]byte, uint64(len(plaintext))+uint64(ctx.Overhead()))[:len(plaintext)]
|
||||
copy(ciphertext, plaintext)
|
||||
ciphertext = padBuffer(ciphertext, ctx.blockCipher.BlockSize())
|
||||
|
||||
cbc := cipher.NewCBCEncrypter(ctx.blockCipher, nonce)
|
||||
|
||||
cbc.CryptBlocks(ciphertext, ciphertext)
|
||||
authtag := ctx.computeAuthTag(data, nonce, ciphertext)
|
||||
|
||||
ret, out := resize(dst, uint64(len(dst))+uint64(len(ciphertext))+uint64(len(authtag)))
|
||||
copy(out, ciphertext)
|
||||
copy(out[len(ciphertext):], authtag)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Open decrypts and authenticates the ciphertext.
|
||||
func (ctx *cbcAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
|
||||
if len(ciphertext) < ctx.authtagBytes {
|
||||
return nil, errors.New("square/go-jose: invalid ciphertext (too short)")
|
||||
}
|
||||
|
||||
offset := len(ciphertext) - ctx.authtagBytes
|
||||
expectedTag := ctx.computeAuthTag(data, nonce, ciphertext[:offset])
|
||||
match := subtle.ConstantTimeCompare(expectedTag, ciphertext[offset:])
|
||||
if match != 1 {
|
||||
return nil, errors.New("square/go-jose: invalid ciphertext (auth tag mismatch)")
|
||||
}
|
||||
|
||||
cbc := cipher.NewCBCDecrypter(ctx.blockCipher, nonce)
|
||||
|
||||
// Make copy of ciphertext buffer, don't want to modify in place
|
||||
buffer := append([]byte{}, []byte(ciphertext[:offset])...)
|
||||
|
||||
if len(buffer)%ctx.blockCipher.BlockSize() > 0 {
|
||||
return nil, errors.New("square/go-jose: invalid ciphertext (invalid length)")
|
||||
}
|
||||
|
||||
cbc.CryptBlocks(buffer, buffer)
|
||||
|
||||
// Remove padding
|
||||
plaintext, err := unpadBuffer(buffer, ctx.blockCipher.BlockSize())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret, out := resize(dst, uint64(len(dst))+uint64(len(plaintext)))
|
||||
copy(out, plaintext)
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Compute an authentication tag
|
||||
func (ctx *cbcAEAD) computeAuthTag(aad, nonce, ciphertext []byte) []byte {
|
||||
buffer := make([]byte, uint64(len(aad))+uint64(len(nonce))+uint64(len(ciphertext))+8)
|
||||
n := 0
|
||||
n += copy(buffer, aad)
|
||||
n += copy(buffer[n:], nonce)
|
||||
n += copy(buffer[n:], ciphertext)
|
||||
binary.BigEndian.PutUint64(buffer[n:], uint64(len(aad))*8)
|
||||
|
||||
// According to documentation, Write() on hash.Hash never fails.
|
||||
hmac := hmac.New(ctx.hash, ctx.integrityKey)
|
||||
_, _ = hmac.Write(buffer)
|
||||
|
||||
return hmac.Sum(nil)[:ctx.authtagBytes]
|
||||
}
|
||||
|
||||
// resize ensures the the given slice has a capacity of at least n bytes.
|
||||
// If the capacity of the slice is less than n, a new slice is allocated
|
||||
// and the existing data will be copied.
|
||||
func resize(in []byte, n uint64) (head, tail []byte) {
|
||||
if uint64(cap(in)) >= n {
|
||||
head = in[:n]
|
||||
} else {
|
||||
head = make([]byte, n)
|
||||
copy(head, in)
|
||||
}
|
||||
|
||||
tail = head[len(in):]
|
||||
return
|
||||
}
|
||||
|
||||
// Apply padding
|
||||
func padBuffer(buffer []byte, blockSize int) []byte {
|
||||
missing := blockSize - (len(buffer) % blockSize)
|
||||
ret, out := resize(buffer, uint64(len(buffer))+uint64(missing))
|
||||
padding := bytes.Repeat([]byte{byte(missing)}, missing)
|
||||
copy(out, padding)
|
||||
return ret
|
||||
}
|
||||
|
||||
// Remove padding
|
||||
func unpadBuffer(buffer []byte, blockSize int) ([]byte, error) {
|
||||
if len(buffer)%blockSize != 0 {
|
||||
return nil, errors.New("square/go-jose: invalid padding")
|
||||
}
|
||||
|
||||
last := buffer[len(buffer)-1]
|
||||
count := int(last)
|
||||
|
||||
if count == 0 || count > blockSize || count > len(buffer) {
|
||||
return nil, errors.New("square/go-jose: invalid padding")
|
||||
}
|
||||
|
||||
padding := bytes.Repeat([]byte{last}, count)
|
||||
if !bytes.HasSuffix(buffer, padding) {
|
||||
return nil, errors.New("square/go-jose: invalid padding")
|
||||
}
|
||||
|
||||
return buffer[:len(buffer)-count], nil
|
||||
}
|
||||
75
vendor/gopkg.in/square/go-jose.v2/cipher/concat_kdf.go
generated
vendored
Normal file
75
vendor/gopkg.in/square/go-jose.v2/cipher/concat_kdf.go
generated
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/*-
|
||||
* Copyright 2014 Square Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package josecipher
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/binary"
|
||||
"hash"
|
||||
"io"
|
||||
)
|
||||
|
||||
type concatKDF struct {
|
||||
z, info []byte
|
||||
i uint32
|
||||
cache []byte
|
||||
hasher hash.Hash
|
||||
}
|
||||
|
||||
// NewConcatKDF builds a KDF reader based on the given inputs.
|
||||
func NewConcatKDF(hash crypto.Hash, z, algID, ptyUInfo, ptyVInfo, supPubInfo, supPrivInfo []byte) io.Reader {
|
||||
buffer := make([]byte, uint64(len(algID))+uint64(len(ptyUInfo))+uint64(len(ptyVInfo))+uint64(len(supPubInfo))+uint64(len(supPrivInfo)))
|
||||
n := 0
|
||||
n += copy(buffer, algID)
|
||||
n += copy(buffer[n:], ptyUInfo)
|
||||
n += copy(buffer[n:], ptyVInfo)
|
||||
n += copy(buffer[n:], supPubInfo)
|
||||
copy(buffer[n:], supPrivInfo)
|
||||
|
||||
hasher := hash.New()
|
||||
|
||||
return &concatKDF{
|
||||
z: z,
|
||||
info: buffer,
|
||||
hasher: hasher,
|
||||
cache: []byte{},
|
||||
i: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *concatKDF) Read(out []byte) (int, error) {
|
||||
copied := copy(out, ctx.cache)
|
||||
ctx.cache = ctx.cache[copied:]
|
||||
|
||||
for copied < len(out) {
|
||||
ctx.hasher.Reset()
|
||||
|
||||
// Write on a hash.Hash never fails
|
||||
_ = binary.Write(ctx.hasher, binary.BigEndian, ctx.i)
|
||||
_, _ = ctx.hasher.Write(ctx.z)
|
||||
_, _ = ctx.hasher.Write(ctx.info)
|
||||
|
||||
hash := ctx.hasher.Sum(nil)
|
||||
chunkCopied := copy(out[copied:], hash)
|
||||
copied += chunkCopied
|
||||
ctx.cache = hash[chunkCopied:]
|
||||
|
||||
ctx.i++
|
||||
}
|
||||
|
||||
return copied, nil
|
||||
}
|
||||
62
vendor/gopkg.in/square/go-jose.v2/cipher/ecdh_es.go
generated
vendored
Normal file
62
vendor/gopkg.in/square/go-jose.v2/cipher/ecdh_es.go
generated
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*-
|
||||
* Copyright 2014 Square Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package josecipher
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// DeriveECDHES derives a shared encryption key using ECDH/ConcatKDF as described in JWE/JWA.
|
||||
// It is an error to call this function with a private/public key that are not on the same
|
||||
// curve. Callers must ensure that the keys are valid before calling this function. Output
|
||||
// size may be at most 1<<16 bytes (64 KiB).
|
||||
func DeriveECDHES(alg string, apuData, apvData []byte, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, size int) []byte {
|
||||
if size > 1<<16 {
|
||||
panic("ECDH-ES output size too large, must be less than or equal to 1<<16")
|
||||
}
|
||||
|
||||
// algId, partyUInfo, partyVInfo inputs must be prefixed with the length
|
||||
algID := lengthPrefixed([]byte(alg))
|
||||
ptyUInfo := lengthPrefixed(apuData)
|
||||
ptyVInfo := lengthPrefixed(apvData)
|
||||
|
||||
// suppPubInfo is the encoded length of the output size in bits
|
||||
supPubInfo := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(supPubInfo, uint32(size)*8)
|
||||
|
||||
if !priv.PublicKey.Curve.IsOnCurve(pub.X, pub.Y) {
|
||||
panic("public key not on same curve as private key")
|
||||
}
|
||||
|
||||
z, _ := priv.PublicKey.Curve.ScalarMult(pub.X, pub.Y, priv.D.Bytes())
|
||||
reader := NewConcatKDF(crypto.SHA256, z.Bytes(), algID, ptyUInfo, ptyVInfo, supPubInfo, []byte{})
|
||||
|
||||
key := make([]byte, size)
|
||||
|
||||
// Read on the KDF will never fail
|
||||
_, _ = reader.Read(key)
|
||||
return key
|
||||
}
|
||||
|
||||
func lengthPrefixed(data []byte) []byte {
|
||||
out := make([]byte, len(data)+4)
|
||||
binary.BigEndian.PutUint32(out, uint32(len(data)))
|
||||
copy(out[4:], data)
|
||||
return out
|
||||
}
|
||||
109
vendor/gopkg.in/square/go-jose.v2/cipher/key_wrap.go
generated
vendored
Normal file
109
vendor/gopkg.in/square/go-jose.v2/cipher/key_wrap.go
generated
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/*-
|
||||
* Copyright 2014 Square Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package josecipher
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var defaultIV = []byte{0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6}
|
||||
|
||||
// KeyWrap implements NIST key wrapping; it wraps a content encryption key (cek) with the given block cipher.
|
||||
func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) {
|
||||
if len(cek)%8 != 0 {
|
||||
return nil, errors.New("square/go-jose: key wrap input must be 8 byte blocks")
|
||||
}
|
||||
|
||||
n := len(cek) / 8
|
||||
r := make([][]byte, n)
|
||||
|
||||
for i := range r {
|
||||
r[i] = make([]byte, 8)
|
||||
copy(r[i], cek[i*8:])
|
||||
}
|
||||
|
||||
buffer := make([]byte, 16)
|
||||
tBytes := make([]byte, 8)
|
||||
copy(buffer, defaultIV)
|
||||
|
||||
for t := 0; t < 6*n; t++ {
|
||||
copy(buffer[8:], r[t%n])
|
||||
|
||||
block.Encrypt(buffer, buffer)
|
||||
|
||||
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
|
||||
|
||||
for i := 0; i < 8; i++ {
|
||||
buffer[i] = buffer[i] ^ tBytes[i]
|
||||
}
|
||||
copy(r[t%n], buffer[8:])
|
||||
}
|
||||
|
||||
out := make([]byte, (n+1)*8)
|
||||
copy(out, buffer[:8])
|
||||
for i := range r {
|
||||
copy(out[(i+1)*8:], r[i])
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// KeyUnwrap implements NIST key unwrapping; it unwraps a content encryption key (cek) with the given block cipher.
|
||||
func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) {
|
||||
if len(ciphertext)%8 != 0 {
|
||||
return nil, errors.New("square/go-jose: key wrap input must be 8 byte blocks")
|
||||
}
|
||||
|
||||
n := (len(ciphertext) / 8) - 1
|
||||
r := make([][]byte, n)
|
||||
|
||||
for i := range r {
|
||||
r[i] = make([]byte, 8)
|
||||
copy(r[i], ciphertext[(i+1)*8:])
|
||||
}
|
||||
|
||||
buffer := make([]byte, 16)
|
||||
tBytes := make([]byte, 8)
|
||||
copy(buffer[:8], ciphertext[:8])
|
||||
|
||||
for t := 6*n - 1; t >= 0; t-- {
|
||||
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
|
||||
|
||||
for i := 0; i < 8; i++ {
|
||||
buffer[i] = buffer[i] ^ tBytes[i]
|
||||
}
|
||||
copy(buffer[8:], r[t%n])
|
||||
|
||||
block.Decrypt(buffer, buffer)
|
||||
|
||||
copy(r[t%n], buffer[8:])
|
||||
}
|
||||
|
||||
if subtle.ConstantTimeCompare(buffer[:8], defaultIV) == 0 {
|
||||
return nil, errors.New("square/go-jose: failed to unwrap key")
|
||||
}
|
||||
|
||||
out := make([]byte, n*8)
|
||||
for i := range r {
|
||||
copy(out[i*8:], r[i])
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
510
vendor/gopkg.in/square/go-jose.v2/crypter.go
generated
vendored
Normal file
510
vendor/gopkg.in/square/go-jose.v2/crypter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,510 @@
|
|||
/*-
|
||||
* Copyright 2014 Square Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jose
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"gopkg.in/square/go-jose.v2/json"
|
||||
)
|
||||
|
||||
// Encrypter represents an encrypter which produces an encrypted JWE object.
|
||||
type Encrypter interface {
|
||||
Encrypt(plaintext []byte) (*JSONWebEncryption, error)
|
||||
EncryptWithAuthData(plaintext []byte, aad []byte) (*JSONWebEncryption, error)
|
||||
Options() EncrypterOptions
|
||||
}
|
||||
|
||||
// A generic content cipher
|
||||
type contentCipher interface {
|
||||
keySize() int
|
||||
encrypt(cek []byte, aad, plaintext []byte) (*aeadParts, error)
|
||||
decrypt(cek []byte, aad []byte, parts *aeadParts) ([]byte, error)
|
||||
}
|
||||
|
||||
// A key generator (for generating/getting a CEK)
|
||||
type keyGenerator interface {
|
||||
keySize() int
|
||||
genKey() ([]byte, rawHeader, error)
|
||||
}
|
||||
|
||||
// A generic key encrypter
|
||||
type keyEncrypter interface {
|
||||
encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) // Encrypt a key
|
||||
}
|
||||
|
||||
// A generic key decrypter
|
||||
type keyDecrypter interface {
|
||||
decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) // Decrypt a key
|
||||
}
|
||||
|
||||
// A generic encrypter based on the given key encrypter and content cipher.
|
||||
type genericEncrypter struct {
|
||||
contentAlg ContentEncryption
|
||||
compressionAlg CompressionAlgorithm
|
||||
cipher contentCipher
|
||||
recipients []recipientKeyInfo
|
||||
keyGenerator keyGenerator
|
||||
extraHeaders map[HeaderKey]interface{}
|
||||
}
|
||||
|
||||
type recipientKeyInfo struct {
|
||||
keyID string
|
||||
keyAlg KeyAlgorithm
|
||||
keyEncrypter keyEncrypter
|
||||
}
|
||||
|
||||
// EncrypterOptions represents options that can be set on new encrypters.
|
||||
type EncrypterOptions struct {
|
||||
Compression CompressionAlgorithm
|
||||
|
||||
// Optional map of additional keys to be inserted into the protected header
|
||||
// of a JWS object. Some specifications which make use of JWS like to insert
|
||||
// additional values here. All values must be JSON-serializable.
|
||||
ExtraHeaders map[HeaderKey]interface{}
|
||||
}
|
||||
|
||||
// WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it
|
||||
// if necessary. It returns itself and so can be used in a fluent style.
|
||||
func (eo *EncrypterOptions) WithHeader(k HeaderKey, v interface{}) *EncrypterOptions {
|
||||
if eo.ExtraHeaders == nil {
|
||||
eo.ExtraHeaders = map[HeaderKey]interface{}{}
|
||||
}
|
||||
eo.ExtraHeaders[k] = v
|
||||
return eo
|
||||
}
|
||||
|
||||
// WithContentType adds a content type ("cty") header and returns the updated
|
||||
// EncrypterOptions.
|
||||
func (eo *EncrypterOptions) WithContentType(contentType ContentType) *EncrypterOptions {
|
||||
return eo.WithHeader(HeaderContentType, contentType)
|
||||
}
|
||||
|
||||
// WithType adds a type ("typ") header and returns the updated EncrypterOptions.
|
||||
func (eo *EncrypterOptions) WithType(typ ContentType) *EncrypterOptions {
|
||||
return eo.WithHeader(HeaderType, typ)
|
||||
}
|
||||
|
||||
// Recipient represents an algorithm/key to encrypt messages to.
|
||||
type Recipient struct {
|
||||
Algorithm KeyAlgorithm
|
||||
Key interface{}
|
||||
KeyID string
|
||||
}
|
||||
|
||||
// NewEncrypter creates an appropriate encrypter based on the key type
|
||||
func NewEncrypter(enc ContentEncryption, rcpt Recipient, opts *EncrypterOptions) (Encrypter, error) {
|
||||
encrypter := &genericEncrypter{
|
||||
contentAlg: enc,
|
||||
recipients: []recipientKeyInfo{},
|
||||
cipher: getContentCipher(enc),
|
||||
}
|
||||
if opts != nil {
|
||||
encrypter.compressionAlg = opts.Compression
|
||||
encrypter.extraHeaders = opts.ExtraHeaders
|
||||
}
|
||||
|
||||
if encrypter.cipher == nil {
|
||||
return nil, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
var keyID string
|
||||
var rawKey interface{}
|
||||
switch encryptionKey := rcpt.Key.(type) {
|
||||
case JSONWebKey:
|
||||
keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key
|
||||
case *JSONWebKey:
|
||||
keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key
|
||||
default:
|
||||
rawKey = encryptionKey
|
||||
}
|
||||
|
||||
switch rcpt.Algorithm {
|
||||
case DIRECT:
|
||||
// Direct encryption mode must be treated differently
|
||||
if reflect.TypeOf(rawKey) != reflect.TypeOf([]byte{}) {
|
||||
return nil, ErrUnsupportedKeyType
|
||||
}
|
||||
encrypter.keyGenerator = staticKeyGenerator{
|
||||
key: rawKey.([]byte),
|
||||
}
|
||||
recipientInfo, _ := newSymmetricRecipient(rcpt.Algorithm, rawKey.([]byte))
|
||||
recipientInfo.keyID = keyID
|
||||
if rcpt.KeyID != "" {
|
||||
recipientInfo.keyID = rcpt.KeyID
|
||||
}
|
||||
encrypter.recipients = []recipientKeyInfo{recipientInfo}
|
||||
return encrypter, nil
|
||||
case ECDH_ES:
|
||||
// ECDH-ES (w/o key wrapping) is similar to DIRECT mode
|
||||
typeOf := reflect.TypeOf(rawKey)
|
||||
if typeOf != reflect.TypeOf(&ecdsa.PublicKey{}) {
|
||||
return nil, ErrUnsupportedKeyType
|
||||
}
|
||||
encrypter.keyGenerator = ecKeyGenerator{
|
||||
size: encrypter.cipher.keySize(),
|
||||
algID: string(enc),
|
||||
publicKey: rawKey.(*ecdsa.PublicKey),
|
||||
}
|
||||
recipientInfo, _ := newECDHRecipient(rcpt.Algorithm, rawKey.(*ecdsa.PublicKey))
|
||||
recipientInfo.keyID = keyID
|
||||
if rcpt.KeyID != "" {
|
||||
recipientInfo.keyID = rcpt.KeyID
|
||||
}
|
||||
encrypter.recipients = []recipientKeyInfo{recipientInfo}
|
||||
return encrypter, nil
|
||||
default:
|
||||
// Can just add a standard recipient
|
||||
encrypter.keyGenerator = randomKeyGenerator{
|
||||
size: encrypter.cipher.keySize(),
|
||||
}
|
||||
err := encrypter.addRecipient(rcpt)
|
||||
return encrypter, err
|
||||
}
|
||||
}
|
||||
|
||||
// NewMultiEncrypter creates a multi-encrypter based on the given parameters
|
||||
func NewMultiEncrypter(enc ContentEncryption, rcpts []Recipient, opts *EncrypterOptions) (Encrypter, error) {
|
||||
cipher := getContentCipher(enc)
|
||||
|
||||
if cipher == nil {
|
||||
return nil, ErrUnsupportedAlgorithm
|
||||
}
|
||||
if rcpts == nil || len(rcpts) == 0 {
|
||||
return nil, fmt.Errorf("square/go-jose: recipients is nil or empty")
|
||||
}
|
||||
|
||||
encrypter := &genericEncrypter{
|
||||
contentAlg: enc,
|
||||
recipients: []recipientKeyInfo{},
|
||||
cipher: cipher,
|
||||
keyGenerator: randomKeyGenerator{
|
||||
size: cipher.keySize(),
|
||||
},
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
encrypter.compressionAlg = opts.Compression
|
||||
}
|
||||
|
||||
for _, recipient := range rcpts {
|
||||
err := encrypter.addRecipient(recipient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return encrypter, nil
|
||||
}
|
||||
|
||||
func (ctx *genericEncrypter) addRecipient(recipient Recipient) (err error) {
|
||||
var recipientInfo recipientKeyInfo
|
||||
|
||||
switch recipient.Algorithm {
|
||||
case DIRECT, ECDH_ES:
|
||||
return fmt.Errorf("square/go-jose: key algorithm '%s' not supported in multi-recipient mode", recipient.Algorithm)
|
||||
}
|
||||
|
||||
recipientInfo, err = makeJWERecipient(recipient.Algorithm, recipient.Key)
|
||||
if recipient.KeyID != "" {
|
||||
recipientInfo.keyID = recipient.KeyID
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
ctx.recipients = append(ctx.recipients, recipientInfo)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func makeJWERecipient(alg KeyAlgorithm, encryptionKey interface{}) (recipientKeyInfo, error) {
|
||||
switch encryptionKey := encryptionKey.(type) {
|
||||
case *rsa.PublicKey:
|
||||
return newRSARecipient(alg, encryptionKey)
|
||||
case *ecdsa.PublicKey:
|
||||
return newECDHRecipient(alg, encryptionKey)
|
||||
case []byte:
|
||||
return newSymmetricRecipient(alg, encryptionKey)
|
||||
case *JSONWebKey:
|
||||
recipient, err := makeJWERecipient(alg, encryptionKey.Key)
|
||||
recipient.keyID = encryptionKey.KeyID
|
||||
return recipient, err
|
||||
default:
|
||||
return recipientKeyInfo{}, ErrUnsupportedKeyType
|
||||
}
|
||||
}
|
||||
|
||||
// newDecrypter creates an appropriate decrypter based on the key type
|
||||
func newDecrypter(decryptionKey interface{}) (keyDecrypter, error) {
|
||||
switch decryptionKey := decryptionKey.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return &rsaDecrypterSigner{
|
||||
privateKey: decryptionKey,
|
||||
}, nil
|
||||
case *ecdsa.PrivateKey:
|
||||
return &ecDecrypterSigner{
|
||||
privateKey: decryptionKey,
|
||||
}, nil
|
||||
case []byte:
|
||||
return &symmetricKeyCipher{
|
||||
key: decryptionKey,
|
||||
}, nil
|
||||
case JSONWebKey:
|
||||
return newDecrypter(decryptionKey.Key)
|
||||
case *JSONWebKey:
|
||||
return newDecrypter(decryptionKey.Key)
|
||||
default:
|
||||
return nil, ErrUnsupportedKeyType
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation of encrypt method producing a JWE object.
|
||||
func (ctx *genericEncrypter) Encrypt(plaintext []byte) (*JSONWebEncryption, error) {
|
||||
return ctx.EncryptWithAuthData(plaintext, nil)
|
||||
}
|
||||
|
||||
// Implementation of encrypt method producing a JWE object.
|
||||
func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JSONWebEncryption, error) {
|
||||
obj := &JSONWebEncryption{}
|
||||
obj.aad = aad
|
||||
|
||||
obj.protected = &rawHeader{}
|
||||
err := obj.protected.set(headerEncryption, ctx.contentAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj.recipients = make([]recipientInfo, len(ctx.recipients))
|
||||
|
||||
if len(ctx.recipients) == 0 {
|
||||
return nil, fmt.Errorf("square/go-jose: no recipients to encrypt to")
|
||||
}
|
||||
|
||||
cek, headers, err := ctx.keyGenerator.genKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj.protected.merge(&headers)
|
||||
|
||||
for i, info := range ctx.recipients {
|
||||
recipient, err := info.keyEncrypter.encryptKey(cek, info.keyAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = recipient.header.set(headerAlgorithm, info.keyAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if info.keyID != "" {
|
||||
err = recipient.header.set(headerKeyID, info.keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
obj.recipients[i] = recipient
|
||||
}
|
||||
|
||||
if len(ctx.recipients) == 1 {
|
||||
// Move per-recipient headers into main protected header if there's
|
||||
// only a single recipient.
|
||||
obj.protected.merge(obj.recipients[0].header)
|
||||
obj.recipients[0].header = nil
|
||||
}
|
||||
|
||||
if ctx.compressionAlg != NONE {
|
||||
plaintext, err = compress(ctx.compressionAlg, plaintext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = obj.protected.set(headerCompression, ctx.compressionAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range ctx.extraHeaders {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
(*obj.protected)[k] = makeRawMessage(b)
|
||||
}
|
||||
|
||||
authData := obj.computeAuthData()
|
||||
parts, err := ctx.cipher.encrypt(cek, authData, plaintext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj.iv = parts.iv
|
||||
obj.ciphertext = parts.ciphertext
|
||||
obj.tag = parts.tag
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func (ctx *genericEncrypter) Options() EncrypterOptions {
|
||||
return EncrypterOptions{
|
||||
Compression: ctx.compressionAlg,
|
||||
ExtraHeaders: ctx.extraHeaders,
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt and validate the object and return the plaintext. Note that this
|
||||
// function does not support multi-recipient, if you desire multi-recipient
|
||||
// decryption use DecryptMulti instead.
|
||||
func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) {
|
||||
headers := obj.mergedHeaders(nil)
|
||||
|
||||
if len(obj.recipients) > 1 {
|
||||
return nil, errors.New("square/go-jose: too many recipients in payload; expecting only one")
|
||||
}
|
||||
|
||||
critical, err := headers.getCritical()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid crit header")
|
||||
}
|
||||
|
||||
if len(critical) > 0 {
|
||||
return nil, fmt.Errorf("square/go-jose: unsupported crit header")
|
||||
}
|
||||
|
||||
decrypter, err := newDecrypter(decryptionKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cipher := getContentCipher(headers.getEncryption())
|
||||
if cipher == nil {
|
||||
return nil, fmt.Errorf("square/go-jose: unsupported enc value '%s'", string(headers.getEncryption()))
|
||||
}
|
||||
|
||||
generator := randomKeyGenerator{
|
||||
size: cipher.keySize(),
|
||||
}
|
||||
|
||||
parts := &aeadParts{
|
||||
iv: obj.iv,
|
||||
ciphertext: obj.ciphertext,
|
||||
tag: obj.tag,
|
||||
}
|
||||
|
||||
authData := obj.computeAuthData()
|
||||
|
||||
var plaintext []byte
|
||||
recipient := obj.recipients[0]
|
||||
recipientHeaders := obj.mergedHeaders(&recipient)
|
||||
|
||||
cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator)
|
||||
if err == nil {
|
||||
// Found a valid CEK -- let's try to decrypt.
|
||||
plaintext, err = cipher.decrypt(cek, authData, parts)
|
||||
}
|
||||
|
||||
if plaintext == nil {
|
||||
return nil, ErrCryptoFailure
|
||||
}
|
||||
|
||||
// The "zip" header parameter may only be present in the protected header.
|
||||
if comp := obj.protected.getCompression(); comp != "" {
|
||||
plaintext, err = decompress(comp, plaintext)
|
||||
}
|
||||
|
||||
return plaintext, err
|
||||
}
|
||||
|
||||
// DecryptMulti decrypts and validates the object and returns the plaintexts,
|
||||
// with support for multiple recipients. It returns the index of the recipient
|
||||
// for which the decryption was successful, the merged headers for that recipient,
|
||||
// and the plaintext.
|
||||
func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) {
|
||||
globalHeaders := obj.mergedHeaders(nil)
|
||||
|
||||
critical, err := globalHeaders.getCritical()
|
||||
if err != nil {
|
||||
return -1, Header{}, nil, fmt.Errorf("square/go-jose: invalid crit header")
|
||||
}
|
||||
|
||||
if len(critical) > 0 {
|
||||
return -1, Header{}, nil, fmt.Errorf("square/go-jose: unsupported crit header")
|
||||
}
|
||||
|
||||
decrypter, err := newDecrypter(decryptionKey)
|
||||
if err != nil {
|
||||
return -1, Header{}, nil, err
|
||||
}
|
||||
|
||||
encryption := globalHeaders.getEncryption()
|
||||
cipher := getContentCipher(encryption)
|
||||
if cipher == nil {
|
||||
return -1, Header{}, nil, fmt.Errorf("square/go-jose: unsupported enc value '%s'", string(encryption))
|
||||
}
|
||||
|
||||
generator := randomKeyGenerator{
|
||||
size: cipher.keySize(),
|
||||
}
|
||||
|
||||
parts := &aeadParts{
|
||||
iv: obj.iv,
|
||||
ciphertext: obj.ciphertext,
|
||||
tag: obj.tag,
|
||||
}
|
||||
|
||||
authData := obj.computeAuthData()
|
||||
|
||||
index := -1
|
||||
var plaintext []byte
|
||||
var headers rawHeader
|
||||
|
||||
for i, recipient := range obj.recipients {
|
||||
recipientHeaders := obj.mergedHeaders(&recipient)
|
||||
|
||||
cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator)
|
||||
if err == nil {
|
||||
// Found a valid CEK -- let's try to decrypt.
|
||||
plaintext, err = cipher.decrypt(cek, authData, parts)
|
||||
if err == nil {
|
||||
index = i
|
||||
headers = recipientHeaders
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if plaintext == nil || err != nil {
|
||||
return -1, Header{}, nil, ErrCryptoFailure
|
||||
}
|
||||
|
||||
// The "zip" header parameter may only be present in the protected header.
|
||||
if comp := obj.protected.getCompression(); comp != "" {
|
||||
plaintext, err = decompress(comp, plaintext)
|
||||
}
|
||||
|
||||
sanitized, err := headers.sanitized()
|
||||
if err != nil {
|
||||
return -1, Header{}, nil, fmt.Errorf("square/go-jose: failed to sanitize header: %v", err)
|
||||
}
|
||||
|
||||
return index, sanitized, plaintext, err
|
||||
}
|
||||
27
vendor/gopkg.in/square/go-jose.v2/doc.go
generated
vendored
Normal file
27
vendor/gopkg.in/square/go-jose.v2/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/*-
|
||||
* Copyright 2014 Square Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
Package jose aims to provide an implementation of the Javascript Object Signing
|
||||
and Encryption set of standards. It implements encryption and signing based on
|
||||
the JSON Web Encryption and JSON Web Signature standards, with optional JSON
|
||||
Web Token support available in a sub-package. The library supports both the
|
||||
compact and full serialization formats, and has optional support for multiple
|
||||
recipients.
|
||||
|
||||
*/
|
||||
package jose
|
||||
178
vendor/gopkg.in/square/go-jose.v2/encoding.go
generated
vendored
Normal file
178
vendor/gopkg.in/square/go-jose.v2/encoding.go
generated
vendored
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
/*-
|
||||
* Copyright 2014 Square Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jose
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"math/big"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var stripWhitespaceRegex = regexp.MustCompile("\\s")
|
||||
|
||||
// Helper function to serialize known-good objects.
|
||||
// Precondition: value is not a nil pointer.
|
||||
func mustSerializeJSON(value interface{}) []byte {
|
||||
out, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// We never want to serialize the top-level value "null," since it's not a
|
||||
// valid JOSE message. But if a caller passes in a nil pointer to this method,
|
||||
// MarshalJSON will happily serialize it as the top-level value "null". If
|
||||
// that value is then embedded in another operation, for instance by being
|
||||
// base64-encoded and fed as input to a signing algorithm
|
||||
// (https://github.com/square/go-jose/issues/22), the result will be
|
||||
// incorrect. Because this method is intended for known-good objects, and a nil
|
||||
// pointer is not a known-good object, we are free to panic in this case.
|
||||
// Note: It's not possible to directly check whether the data pointed at by an
|
||||
// interface is a nil pointer, so we do this hacky workaround.
|
||||
// https://groups.google.com/forum/#!topic/golang-nuts/wnH302gBa4I
|
||||
if string(out) == "null" {
|
||||
panic("Tried to serialize a nil pointer.")
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Strip all newlines and whitespace
|
||||
func stripWhitespace(data string) string {
|
||||
return stripWhitespaceRegex.ReplaceAllString(data, "")
|
||||
}
|
||||
|
||||
// Perform compression based on algorithm
|
||||
func compress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) {
|
||||
switch algorithm {
|
||||
case DEFLATE:
|
||||
return deflate(input)
|
||||
default:
|
||||
return nil, ErrUnsupportedAlgorithm
|
||||
}
|
||||
}
|
||||
|
||||
// Perform decompression based on algorithm
|
||||
func decompress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) {
|
||||
switch algorithm {
|
||||
case DEFLATE:
|
||||
return inflate(input)
|
||||
default:
|
||||
return nil, ErrUnsupportedAlgorithm
|
||||
}
|
||||
}
|
||||
|
||||
// Compress with DEFLATE
|
||||
func deflate(input []byte) ([]byte, error) {
|
||||
output := new(bytes.Buffer)
|
||||
|
||||
// Writing to byte buffer, err is always nil
|
||||
writer, _ := flate.NewWriter(output, 1)
|
||||
_, _ = io.Copy(writer, bytes.NewBuffer(input))
|
||||
|
||||
err := writer.Close()
|
||||
return output.Bytes(), err
|
||||
}
|
||||
|
||||
// Decompress with DEFLATE
|
||||
func inflate(input []byte) ([]byte, error) {
|
||||
output := new(bytes.Buffer)
|
||||
reader := flate.NewReader(bytes.NewBuffer(input))
|
||||
|
||||
_, err := io.Copy(output, reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = reader.Close()
|
||||
return output.Bytes(), err
|
||||
}
|
||||
|
||||
// byteBuffer represents a slice of bytes that can be serialized to url-safe base64.
|
||||
type byteBuffer struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func newBuffer(data []byte) *byteBuffer {
|
||||
if data == nil {
|
||||
return nil
|
||||
}
|
||||
return &byteBuffer{
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func newFixedSizeBuffer(data []byte, length int) *byteBuffer {
|
||||
if len(data) > length {
|
||||
panic("square/go-jose: invalid call to newFixedSizeBuffer (len(data) > length)")
|
||||
}
|
||||
pad := make([]byte, length-len(data))
|
||||
return newBuffer(append(pad, data...))
|
||||
}
|
||||
|
||||
func newBufferFromInt(num uint64) *byteBuffer {
|
||||
data := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(data, num)
|
||||
return newBuffer(bytes.TrimLeft(data, "\x00"))
|
||||
}
|
||||
|
||||
func (b *byteBuffer) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(b.base64())
|
||||
}
|
||||
|
||||
func (b *byteBuffer) UnmarshalJSON(data []byte) error {
|
||||
var encoded string
|
||||
err := json.Unmarshal(data, &encoded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if encoded == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(encoded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*b = *newBuffer(decoded)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *byteBuffer) base64() string {
|
||||
return base64.RawURLEncoding.EncodeToString(b.data)
|
||||
}
|
||||
|
||||
func (b *byteBuffer) bytes() []byte {
|
||||
// Handling nil here allows us to transparently handle nil slices when serializing.
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
return b.data
|
||||
}
|
||||
|
||||
func (b byteBuffer) bigInt() *big.Int {
|
||||
return new(big.Int).SetBytes(b.data)
|
||||
}
|
||||
|
||||
func (b byteBuffer) toInt() int {
|
||||
return int(b.bigInt().Int64())
|
||||
}
|
||||
27
vendor/gopkg.in/square/go-jose.v2/json/LICENSE
generated
vendored
Normal file
27
vendor/gopkg.in/square/go-jose.v2/json/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
13
vendor/gopkg.in/square/go-jose.v2/json/README.md
generated
vendored
Normal file
13
vendor/gopkg.in/square/go-jose.v2/json/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Safe JSON
|
||||
|
||||
This repository contains a fork of the `encoding/json` package from Go 1.6.
|
||||
|
||||
The following changes were made:
|
||||
|
||||
* Object deserialization uses case-sensitive member name matching instead of
|
||||
[case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html).
|
||||
This is to avoid differences in the interpretation of JOSE messages between
|
||||
go-jose and libraries written in other languages.
|
||||
* When deserializing a JSON object, we check for duplicate keys and reject the
|
||||
input whenever we detect a duplicate. Rather than trying to work with malformed
|
||||
data, we prefer to reject it right away.
|
||||
1183
vendor/gopkg.in/square/go-jose.v2/json/decode.go
generated
vendored
Normal file
1183
vendor/gopkg.in/square/go-jose.v2/json/decode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1197
vendor/gopkg.in/square/go-jose.v2/json/encode.go
generated
vendored
Normal file
1197
vendor/gopkg.in/square/go-jose.v2/json/encode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
141
vendor/gopkg.in/square/go-jose.v2/json/indent.go
generated
vendored
Normal file
141
vendor/gopkg.in/square/go-jose.v2/json/indent.go
generated
vendored
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import "bytes"
|
||||
|
||||
// Compact appends to dst the JSON-encoded src with
|
||||
// insignificant space characters elided.
|
||||
func Compact(dst *bytes.Buffer, src []byte) error {
|
||||
return compact(dst, src, false)
|
||||
}
|
||||
|
||||
func compact(dst *bytes.Buffer, src []byte, escape bool) error {
|
||||
origLen := dst.Len()
|
||||
var scan scanner
|
||||
scan.reset()
|
||||
start := 0
|
||||
for i, c := range src {
|
||||
if escape && (c == '<' || c == '>' || c == '&') {
|
||||
if start < i {
|
||||
dst.Write(src[start:i])
|
||||
}
|
||||
dst.WriteString(`\u00`)
|
||||
dst.WriteByte(hex[c>>4])
|
||||
dst.WriteByte(hex[c&0xF])
|
||||
start = i + 1
|
||||
}
|
||||
// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
|
||||
if c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
|
||||
if start < i {
|
||||
dst.Write(src[start:i])
|
||||
}
|
||||
dst.WriteString(`\u202`)
|
||||
dst.WriteByte(hex[src[i+2]&0xF])
|
||||
start = i + 3
|
||||
}
|
||||
v := scan.step(&scan, c)
|
||||
if v >= scanSkipSpace {
|
||||
if v == scanError {
|
||||
break
|
||||
}
|
||||
if start < i {
|
||||
dst.Write(src[start:i])
|
||||
}
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
if scan.eof() == scanError {
|
||||
dst.Truncate(origLen)
|
||||
return scan.err
|
||||
}
|
||||
if start < len(src) {
|
||||
dst.Write(src[start:])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newline(dst *bytes.Buffer, prefix, indent string, depth int) {
|
||||
dst.WriteByte('\n')
|
||||
dst.WriteString(prefix)
|
||||
for i := 0; i < depth; i++ {
|
||||
dst.WriteString(indent)
|
||||
}
|
||||
}
|
||||
|
||||
// Indent appends to dst an indented form of the JSON-encoded src.
|
||||
// Each element in a JSON object or array begins on a new,
|
||||
// indented line beginning with prefix followed by one or more
|
||||
// copies of indent according to the indentation nesting.
|
||||
// The data appended to dst does not begin with the prefix nor
|
||||
// any indentation, to make it easier to embed inside other formatted JSON data.
|
||||
// Although leading space characters (space, tab, carriage return, newline)
|
||||
// at the beginning of src are dropped, trailing space characters
|
||||
// at the end of src are preserved and copied to dst.
|
||||
// For example, if src has no trailing spaces, neither will dst;
|
||||
// if src ends in a trailing newline, so will dst.
|
||||
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
|
||||
origLen := dst.Len()
|
||||
var scan scanner
|
||||
scan.reset()
|
||||
needIndent := false
|
||||
depth := 0
|
||||
for _, c := range src {
|
||||
scan.bytes++
|
||||
v := scan.step(&scan, c)
|
||||
if v == scanSkipSpace {
|
||||
continue
|
||||
}
|
||||
if v == scanError {
|
||||
break
|
||||
}
|
||||
if needIndent && v != scanEndObject && v != scanEndArray {
|
||||
needIndent = false
|
||||
depth++
|
||||
newline(dst, prefix, indent, depth)
|
||||
}
|
||||
|
||||
// Emit semantically uninteresting bytes
|
||||
// (in particular, punctuation in strings) unmodified.
|
||||
if v == scanContinue {
|
||||
dst.WriteByte(c)
|
||||
continue
|
||||
}
|
||||
|
||||
// Add spacing around real punctuation.
|
||||
switch c {
|
||||
case '{', '[':
|
||||
// delay indent so that empty object and array are formatted as {} and [].
|
||||
needIndent = true
|
||||
dst.WriteByte(c)
|
||||
|
||||
case ',':
|
||||
dst.WriteByte(c)
|
||||
newline(dst, prefix, indent, depth)
|
||||
|
||||
case ':':
|
||||
dst.WriteByte(c)
|
||||
dst.WriteByte(' ')
|
||||
|
||||
case '}', ']':
|
||||
if needIndent {
|
||||
// suppress indent in empty object/array
|
||||
needIndent = false
|
||||
} else {
|
||||
depth--
|
||||
newline(dst, prefix, indent, depth)
|
||||
}
|
||||
dst.WriteByte(c)
|
||||
|
||||
default:
|
||||
dst.WriteByte(c)
|
||||
}
|
||||
}
|
||||
if scan.eof() == scanError {
|
||||
dst.Truncate(origLen)
|
||||
return scan.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
623
vendor/gopkg.in/square/go-jose.v2/json/scanner.go
generated
vendored
Normal file
623
vendor/gopkg.in/square/go-jose.v2/json/scanner.go
generated
vendored
Normal file
|
|
@ -0,0 +1,623 @@
|
|||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
// JSON value parser state machine.
|
||||
// Just about at the limit of what is reasonable to write by hand.
|
||||
// Some parts are a bit tedious, but overall it nicely factors out the
|
||||
// otherwise common code from the multiple scanning functions
|
||||
// in this package (Compact, Indent, checkValid, nextValue, etc).
|
||||
//
|
||||
// This file starts with two simple examples using the scanner
|
||||
// before diving into the scanner itself.
|
||||
|
||||
import "strconv"
|
||||
|
||||
// checkValid verifies that data is valid JSON-encoded data.
|
||||
// scan is passed in for use by checkValid to avoid an allocation.
|
||||
func checkValid(data []byte, scan *scanner) error {
|
||||
scan.reset()
|
||||
for _, c := range data {
|
||||
scan.bytes++
|
||||
if scan.step(scan, c) == scanError {
|
||||
return scan.err
|
||||
}
|
||||
}
|
||||
if scan.eof() == scanError {
|
||||
return scan.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextValue splits data after the next whole JSON value,
|
||||
// returning that value and the bytes that follow it as separate slices.
|
||||
// scan is passed in for use by nextValue to avoid an allocation.
|
||||
func nextValue(data []byte, scan *scanner) (value, rest []byte, err error) {
|
||||
scan.reset()
|
||||
for i, c := range data {
|
||||
v := scan.step(scan, c)
|
||||
if v >= scanEndObject {
|
||||
switch v {
|
||||
// probe the scanner with a space to determine whether we will
|
||||
// get scanEnd on the next character. Otherwise, if the next character
|
||||
// is not a space, scanEndTop allocates a needless error.
|
||||
case scanEndObject, scanEndArray:
|
||||
if scan.step(scan, ' ') == scanEnd {
|
||||
return data[:i+1], data[i+1:], nil
|
||||
}
|
||||
case scanError:
|
||||
return nil, nil, scan.err
|
||||
case scanEnd:
|
||||
return data[:i], data[i:], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if scan.eof() == scanError {
|
||||
return nil, nil, scan.err
|
||||
}
|
||||
return data, nil, nil
|
||||
}
|
||||
|
||||
// A SyntaxError is a description of a JSON syntax error.
|
||||
type SyntaxError struct {
|
||||
msg string // description of error
|
||||
Offset int64 // error occurred after reading Offset bytes
|
||||
}
|
||||
|
||||
func (e *SyntaxError) Error() string { return e.msg }
|
||||
|
||||
// A scanner is a JSON scanning state machine.
|
||||
// Callers call scan.reset() and then pass bytes in one at a time
|
||||
// by calling scan.step(&scan, c) for each byte.
|
||||
// The return value, referred to as an opcode, tells the
|
||||
// caller about significant parsing events like beginning
|
||||
// and ending literals, objects, and arrays, so that the
|
||||
// caller can follow along if it wishes.
|
||||
// The return value scanEnd indicates that a single top-level
|
||||
// JSON value has been completed, *before* the byte that
|
||||
// just got passed in. (The indication must be delayed in order
|
||||
// to recognize the end of numbers: is 123 a whole value or
|
||||
// the beginning of 12345e+6?).
|
||||
type scanner struct {
|
||||
// The step is a func to be called to execute the next transition.
|
||||
// Also tried using an integer constant and a single func
|
||||
// with a switch, but using the func directly was 10% faster
|
||||
// on a 64-bit Mac Mini, and it's nicer to read.
|
||||
step func(*scanner, byte) int
|
||||
|
||||
// Reached end of top-level value.
|
||||
endTop bool
|
||||
|
||||
// Stack of what we're in the middle of - array values, object keys, object values.
|
||||
parseState []int
|
||||
|
||||
// Error that happened, if any.
|
||||
err error
|
||||
|
||||
// 1-byte redo (see undo method)
|
||||
redo bool
|
||||
redoCode int
|
||||
redoState func(*scanner, byte) int
|
||||
|
||||
// total bytes consumed, updated by decoder.Decode
|
||||
bytes int64
|
||||
}
|
||||
|
||||
// These values are returned by the state transition functions
|
||||
// assigned to scanner.state and the method scanner.eof.
|
||||
// They give details about the current state of the scan that
|
||||
// callers might be interested to know about.
|
||||
// It is okay to ignore the return value of any particular
|
||||
// call to scanner.state: if one call returns scanError,
|
||||
// every subsequent call will return scanError too.
|
||||
const (
|
||||
// Continue.
|
||||
scanContinue = iota // uninteresting byte
|
||||
scanBeginLiteral // end implied by next result != scanContinue
|
||||
scanBeginObject // begin object
|
||||
scanObjectKey // just finished object key (string)
|
||||
scanObjectValue // just finished non-last object value
|
||||
scanEndObject // end object (implies scanObjectValue if possible)
|
||||
scanBeginArray // begin array
|
||||
scanArrayValue // just finished array value
|
||||
scanEndArray // end array (implies scanArrayValue if possible)
|
||||
scanSkipSpace // space byte; can skip; known to be last "continue" result
|
||||
|
||||
// Stop.
|
||||
scanEnd // top-level value ended *before* this byte; known to be first "stop" result
|
||||
scanError // hit an error, scanner.err.
|
||||
)
|
||||
|
||||
// These values are stored in the parseState stack.
|
||||
// They give the current state of a composite value
|
||||
// being scanned. If the parser is inside a nested value
|
||||
// the parseState describes the nested state, outermost at entry 0.
|
||||
const (
|
||||
parseObjectKey = iota // parsing object key (before colon)
|
||||
parseObjectValue // parsing object value (after colon)
|
||||
parseArrayValue // parsing array value
|
||||
)
|
||||
|
||||
// reset prepares the scanner for use.
|
||||
// It must be called before calling s.step.
|
||||
func (s *scanner) reset() {
|
||||
s.step = stateBeginValue
|
||||
s.parseState = s.parseState[0:0]
|
||||
s.err = nil
|
||||
s.redo = false
|
||||
s.endTop = false
|
||||
}
|
||||
|
||||
// eof tells the scanner that the end of input has been reached.
|
||||
// It returns a scan status just as s.step does.
|
||||
func (s *scanner) eof() int {
|
||||
if s.err != nil {
|
||||
return scanError
|
||||
}
|
||||
if s.endTop {
|
||||
return scanEnd
|
||||
}
|
||||
s.step(s, ' ')
|
||||
if s.endTop {
|
||||
return scanEnd
|
||||
}
|
||||
if s.err == nil {
|
||||
s.err = &SyntaxError{"unexpected end of JSON input", s.bytes}
|
||||
}
|
||||
return scanError
|
||||
}
|
||||
|
||||
// pushParseState pushes a new parse state p onto the parse stack.
|
||||
func (s *scanner) pushParseState(p int) {
|
||||
s.parseState = append(s.parseState, p)
|
||||
}
|
||||
|
||||
// popParseState pops a parse state (already obtained) off the stack
|
||||
// and updates s.step accordingly.
|
||||
func (s *scanner) popParseState() {
|
||||
n := len(s.parseState) - 1
|
||||
s.parseState = s.parseState[0:n]
|
||||
s.redo = false
|
||||
if n == 0 {
|
||||
s.step = stateEndTop
|
||||
s.endTop = true
|
||||
} else {
|
||||
s.step = stateEndValue
|
||||
}
|
||||
}
|
||||
|
||||
func isSpace(c byte) bool {
|
||||
return c == ' ' || c == '\t' || c == '\r' || c == '\n'
|
||||
}
|
||||
|
||||
// stateBeginValueOrEmpty is the state after reading `[`.
|
||||
func stateBeginValueOrEmpty(s *scanner, c byte) int {
|
||||
if c <= ' ' && isSpace(c) {
|
||||
return scanSkipSpace
|
||||
}
|
||||
if c == ']' {
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
return stateBeginValue(s, c)
|
||||
}
|
||||
|
||||
// stateBeginValue is the state at the beginning of the input.
|
||||
func stateBeginValue(s *scanner, c byte) int {
|
||||
if c <= ' ' && isSpace(c) {
|
||||
return scanSkipSpace
|
||||
}
|
||||
switch c {
|
||||
case '{':
|
||||
s.step = stateBeginStringOrEmpty
|
||||
s.pushParseState(parseObjectKey)
|
||||
return scanBeginObject
|
||||
case '[':
|
||||
s.step = stateBeginValueOrEmpty
|
||||
s.pushParseState(parseArrayValue)
|
||||
return scanBeginArray
|
||||
case '"':
|
||||
s.step = stateInString
|
||||
return scanBeginLiteral
|
||||
case '-':
|
||||
s.step = stateNeg
|
||||
return scanBeginLiteral
|
||||
case '0': // beginning of 0.123
|
||||
s.step = state0
|
||||
return scanBeginLiteral
|
||||
case 't': // beginning of true
|
||||
s.step = stateT
|
||||
return scanBeginLiteral
|
||||
case 'f': // beginning of false
|
||||
s.step = stateF
|
||||
return scanBeginLiteral
|
||||
case 'n': // beginning of null
|
||||
s.step = stateN
|
||||
return scanBeginLiteral
|
||||
}
|
||||
if '1' <= c && c <= '9' { // beginning of 1234.5
|
||||
s.step = state1
|
||||
return scanBeginLiteral
|
||||
}
|
||||
return s.error(c, "looking for beginning of value")
|
||||
}
|
||||
|
||||
// stateBeginStringOrEmpty is the state after reading `{`.
|
||||
func stateBeginStringOrEmpty(s *scanner, c byte) int {
|
||||
if c <= ' ' && isSpace(c) {
|
||||
return scanSkipSpace
|
||||
}
|
||||
if c == '}' {
|
||||
n := len(s.parseState)
|
||||
s.parseState[n-1] = parseObjectValue
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
return stateBeginString(s, c)
|
||||
}
|
||||
|
||||
// stateBeginString is the state after reading `{"key": value,`.
|
||||
func stateBeginString(s *scanner, c byte) int {
|
||||
if c <= ' ' && isSpace(c) {
|
||||
return scanSkipSpace
|
||||
}
|
||||
if c == '"' {
|
||||
s.step = stateInString
|
||||
return scanBeginLiteral
|
||||
}
|
||||
return s.error(c, "looking for beginning of object key string")
|
||||
}
|
||||
|
||||
// stateEndValue is the state after completing a value,
|
||||
// such as after reading `{}` or `true` or `["x"`.
|
||||
func stateEndValue(s *scanner, c byte) int {
|
||||
n := len(s.parseState)
|
||||
if n == 0 {
|
||||
// Completed top-level before the current byte.
|
||||
s.step = stateEndTop
|
||||
s.endTop = true
|
||||
return stateEndTop(s, c)
|
||||
}
|
||||
if c <= ' ' && isSpace(c) {
|
||||
s.step = stateEndValue
|
||||
return scanSkipSpace
|
||||
}
|
||||
ps := s.parseState[n-1]
|
||||
switch ps {
|
||||
case parseObjectKey:
|
||||
if c == ':' {
|
||||
s.parseState[n-1] = parseObjectValue
|
||||
s.step = stateBeginValue
|
||||
return scanObjectKey
|
||||
}
|
||||
return s.error(c, "after object key")
|
||||
case parseObjectValue:
|
||||
if c == ',' {
|
||||
s.parseState[n-1] = parseObjectKey
|
||||
s.step = stateBeginString
|
||||
return scanObjectValue
|
||||
}
|
||||
if c == '}' {
|
||||
s.popParseState()
|
||||
return scanEndObject
|
||||
}
|
||||
return s.error(c, "after object key:value pair")
|
||||
case parseArrayValue:
|
||||
if c == ',' {
|
||||
s.step = stateBeginValue
|
||||
return scanArrayValue
|
||||
}
|
||||
if c == ']' {
|
||||
s.popParseState()
|
||||
return scanEndArray
|
||||
}
|
||||
return s.error(c, "after array element")
|
||||
}
|
||||
return s.error(c, "")
|
||||
}
|
||||
|
||||
// stateEndTop is the state after finishing the top-level value,
|
||||
// such as after reading `{}` or `[1,2,3]`.
|
||||
// Only space characters should be seen now.
|
||||
func stateEndTop(s *scanner, c byte) int {
|
||||
if c != ' ' && c != '\t' && c != '\r' && c != '\n' {
|
||||
// Complain about non-space byte on next call.
|
||||
s.error(c, "after top-level value")
|
||||
}
|
||||
return scanEnd
|
||||
}
|
||||
|
||||
// stateInString is the state after reading `"`.
|
||||
func stateInString(s *scanner, c byte) int {
|
||||
if c == '"' {
|
||||
s.step = stateEndValue
|
||||
return scanContinue
|
||||
}
|
||||
if c == '\\' {
|
||||
s.step = stateInStringEsc
|
||||
return scanContinue
|
||||
}
|
||||
if c < 0x20 {
|
||||
return s.error(c, "in string literal")
|
||||
}
|
||||
return scanContinue
|
||||
}
|
||||
|
||||
// stateInStringEsc is the state after reading `"\` during a quoted string.
|
||||
func stateInStringEsc(s *scanner, c byte) int {
|
||||
switch c {
|
||||
case 'b', 'f', 'n', 'r', 't', '\\', '/', '"':
|
||||
s.step = stateInString
|
||||
return scanContinue
|
||||
case 'u':
|
||||
s.step = stateInStringEscU
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in string escape code")
|
||||
}
|
||||
|
||||
// stateInStringEscU is the state after reading `"\u` during a quoted string.
|
||||
func stateInStringEscU(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||
s.step = stateInStringEscU1
|
||||
return scanContinue
|
||||
}
|
||||
// numbers
|
||||
return s.error(c, "in \\u hexadecimal character escape")
|
||||
}
|
||||
|
||||
// stateInStringEscU1 is the state after reading `"\u1` during a quoted string.
|
||||
func stateInStringEscU1(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||
s.step = stateInStringEscU12
|
||||
return scanContinue
|
||||
}
|
||||
// numbers
|
||||
return s.error(c, "in \\u hexadecimal character escape")
|
||||
}
|
||||
|
||||
// stateInStringEscU12 is the state after reading `"\u12` during a quoted string.
|
||||
func stateInStringEscU12(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||
s.step = stateInStringEscU123
|
||||
return scanContinue
|
||||
}
|
||||
// numbers
|
||||
return s.error(c, "in \\u hexadecimal character escape")
|
||||
}
|
||||
|
||||
// stateInStringEscU123 is the state after reading `"\u123` during a quoted string.
|
||||
func stateInStringEscU123(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||
s.step = stateInString
|
||||
return scanContinue
|
||||
}
|
||||
// numbers
|
||||
return s.error(c, "in \\u hexadecimal character escape")
|
||||
}
|
||||
|
||||
// stateNeg is the state after reading `-` during a number.
|
||||
func stateNeg(s *scanner, c byte) int {
|
||||
if c == '0' {
|
||||
s.step = state0
|
||||
return scanContinue
|
||||
}
|
||||
if '1' <= c && c <= '9' {
|
||||
s.step = state1
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in numeric literal")
|
||||
}
|
||||
|
||||
// state1 is the state after reading a non-zero integer during a number,
|
||||
// such as after reading `1` or `100` but not `0`.
|
||||
func state1(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
s.step = state1
|
||||
return scanContinue
|
||||
}
|
||||
return state0(s, c)
|
||||
}
|
||||
|
||||
// state0 is the state after reading `0` during a number.
|
||||
func state0(s *scanner, c byte) int {
|
||||
if c == '.' {
|
||||
s.step = stateDot
|
||||
return scanContinue
|
||||
}
|
||||
if c == 'e' || c == 'E' {
|
||||
s.step = stateE
|
||||
return scanContinue
|
||||
}
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
|
||||
// stateDot is the state after reading the integer and decimal point in a number,
|
||||
// such as after reading `1.`.
|
||||
func stateDot(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
s.step = stateDot0
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "after decimal point in numeric literal")
|
||||
}
|
||||
|
||||
// stateDot0 is the state after reading the integer, decimal point, and subsequent
|
||||
// digits of a number, such as after reading `3.14`.
|
||||
func stateDot0(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
return scanContinue
|
||||
}
|
||||
if c == 'e' || c == 'E' {
|
||||
s.step = stateE
|
||||
return scanContinue
|
||||
}
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
|
||||
// stateE is the state after reading the mantissa and e in a number,
|
||||
// such as after reading `314e` or `0.314e`.
|
||||
func stateE(s *scanner, c byte) int {
|
||||
if c == '+' || c == '-' {
|
||||
s.step = stateESign
|
||||
return scanContinue
|
||||
}
|
||||
return stateESign(s, c)
|
||||
}
|
||||
|
||||
// stateESign is the state after reading the mantissa, e, and sign in a number,
|
||||
// such as after reading `314e-` or `0.314e+`.
|
||||
func stateESign(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
s.step = stateE0
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in exponent of numeric literal")
|
||||
}
|
||||
|
||||
// stateE0 is the state after reading the mantissa, e, optional sign,
|
||||
// and at least one digit of the exponent in a number,
|
||||
// such as after reading `314e-2` or `0.314e+1` or `3.14e0`.
|
||||
func stateE0(s *scanner, c byte) int {
|
||||
if '0' <= c && c <= '9' {
|
||||
return scanContinue
|
||||
}
|
||||
return stateEndValue(s, c)
|
||||
}
|
||||
|
||||
// stateT is the state after reading `t`.
|
||||
func stateT(s *scanner, c byte) int {
|
||||
if c == 'r' {
|
||||
s.step = stateTr
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal true (expecting 'r')")
|
||||
}
|
||||
|
||||
// stateTr is the state after reading `tr`.
|
||||
func stateTr(s *scanner, c byte) int {
|
||||
if c == 'u' {
|
||||
s.step = stateTru
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal true (expecting 'u')")
|
||||
}
|
||||
|
||||
// stateTru is the state after reading `tru`.
|
||||
func stateTru(s *scanner, c byte) int {
|
||||
if c == 'e' {
|
||||
s.step = stateEndValue
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal true (expecting 'e')")
|
||||
}
|
||||
|
||||
// stateF is the state after reading `f`.
|
||||
func stateF(s *scanner, c byte) int {
|
||||
if c == 'a' {
|
||||
s.step = stateFa
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal false (expecting 'a')")
|
||||
}
|
||||
|
||||
// stateFa is the state after reading `fa`.
|
||||
func stateFa(s *scanner, c byte) int {
|
||||
if c == 'l' {
|
||||
s.step = stateFal
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal false (expecting 'l')")
|
||||
}
|
||||
|
||||
// stateFal is the state after reading `fal`.
|
||||
func stateFal(s *scanner, c byte) int {
|
||||
if c == 's' {
|
||||
s.step = stateFals
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal false (expecting 's')")
|
||||
}
|
||||
|
||||
// stateFals is the state after reading `fals`.
|
||||
func stateFals(s *scanner, c byte) int {
|
||||
if c == 'e' {
|
||||
s.step = stateEndValue
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal false (expecting 'e')")
|
||||
}
|
||||
|
||||
// stateN is the state after reading `n`.
|
||||
func stateN(s *scanner, c byte) int {
|
||||
if c == 'u' {
|
||||
s.step = stateNu
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal null (expecting 'u')")
|
||||
}
|
||||
|
||||
// stateNu is the state after reading `nu`.
|
||||
func stateNu(s *scanner, c byte) int {
|
||||
if c == 'l' {
|
||||
s.step = stateNul
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal null (expecting 'l')")
|
||||
}
|
||||
|
||||
// stateNul is the state after reading `nul`.
|
||||
func stateNul(s *scanner, c byte) int {
|
||||
if c == 'l' {
|
||||
s.step = stateEndValue
|
||||
return scanContinue
|
||||
}
|
||||
return s.error(c, "in literal null (expecting 'l')")
|
||||
}
|
||||
|
||||
// stateError is the state after reaching a syntax error,
|
||||
// such as after reading `[1}` or `5.1.2`.
|
||||
func stateError(s *scanner, c byte) int {
|
||||
return scanError
|
||||
}
|
||||
|
||||
// error records an error and switches to the error state.
|
||||
func (s *scanner) error(c byte, context string) int {
|
||||
s.step = stateError
|
||||
s.err = &SyntaxError{"invalid character " + quoteChar(c) + " " + context, s.bytes}
|
||||
return scanError
|
||||
}
|
||||
|
||||
// quoteChar formats c as a quoted character literal
|
||||
func quoteChar(c byte) string {
|
||||
// special cases - different from quoted strings
|
||||
if c == '\'' {
|
||||
return `'\''`
|
||||
}
|
||||
if c == '"' {
|
||||
return `'"'`
|
||||
}
|
||||
|
||||
// use quoted string with different quotation marks
|
||||
s := strconv.Quote(string(c))
|
||||
return "'" + s[1:len(s)-1] + "'"
|
||||
}
|
||||
|
||||
// undo causes the scanner to return scanCode from the next state transition.
|
||||
// This gives callers a simple 1-byte undo mechanism.
|
||||
func (s *scanner) undo(scanCode int) {
|
||||
if s.redo {
|
||||
panic("json: invalid use of scanner")
|
||||
}
|
||||
s.redoCode = scanCode
|
||||
s.redoState = s.step
|
||||
s.step = stateRedo
|
||||
s.redo = true
|
||||
}
|
||||
|
||||
// stateRedo helps implement the scanner's 1-byte undo.
|
||||
func stateRedo(s *scanner, c byte) int {
|
||||
s.redo = false
|
||||
s.step = s.redoState
|
||||
return s.redoCode
|
||||
}
|
||||
480
vendor/gopkg.in/square/go-jose.v2/json/stream.go
generated
vendored
Normal file
480
vendor/gopkg.in/square/go-jose.v2/json/stream.go
generated
vendored
Normal file
|
|
@ -0,0 +1,480 @@
|
|||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// A Decoder reads and decodes JSON objects from an input stream.
|
||||
type Decoder struct {
|
||||
r io.Reader
|
||||
buf []byte
|
||||
d decodeState
|
||||
scanp int // start of unread data in buf
|
||||
scan scanner
|
||||
err error
|
||||
|
||||
tokenState int
|
||||
tokenStack []int
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder that reads from r.
|
||||
//
|
||||
// The decoder introduces its own buffering and may
|
||||
// read data from r beyond the JSON values requested.
|
||||
func NewDecoder(r io.Reader) *Decoder {
|
||||
return &Decoder{r: r}
|
||||
}
|
||||
|
||||
// UseNumber causes the Decoder to unmarshal a number into an interface{} as a
|
||||
// Number instead of as a float64.
|
||||
func (dec *Decoder) UseNumber() { dec.d.useNumber = true }
|
||||
|
||||
// Decode reads the next JSON-encoded value from its
|
||||
// input and stores it in the value pointed to by v.
|
||||
//
|
||||
// See the documentation for Unmarshal for details about
|
||||
// the conversion of JSON into a Go value.
|
||||
func (dec *Decoder) Decode(v interface{}) error {
|
||||
if dec.err != nil {
|
||||
return dec.err
|
||||
}
|
||||
|
||||
if err := dec.tokenPrepareForDecode(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !dec.tokenValueAllowed() {
|
||||
return &SyntaxError{msg: "not at beginning of value"}
|
||||
}
|
||||
|
||||
// Read whole value into buffer.
|
||||
n, err := dec.readValue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dec.d.init(dec.buf[dec.scanp : dec.scanp+n])
|
||||
dec.scanp += n
|
||||
|
||||
// Don't save err from unmarshal into dec.err:
|
||||
// the connection is still usable since we read a complete JSON
|
||||
// object from it before the error happened.
|
||||
err = dec.d.unmarshal(v)
|
||||
|
||||
// fixup token streaming state
|
||||
dec.tokenValueEnd()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Buffered returns a reader of the data remaining in the Decoder's
|
||||
// buffer. The reader is valid until the next call to Decode.
|
||||
func (dec *Decoder) Buffered() io.Reader {
|
||||
return bytes.NewReader(dec.buf[dec.scanp:])
|
||||
}
|
||||
|
||||
// readValue reads a JSON value into dec.buf.
|
||||
// It returns the length of the encoding.
|
||||
func (dec *Decoder) readValue() (int, error) {
|
||||
dec.scan.reset()
|
||||
|
||||
scanp := dec.scanp
|
||||
var err error
|
||||
Input:
|
||||
for {
|
||||
// Look in the buffer for a new value.
|
||||
for i, c := range dec.buf[scanp:] {
|
||||
dec.scan.bytes++
|
||||
v := dec.scan.step(&dec.scan, c)
|
||||
if v == scanEnd {
|
||||
scanp += i
|
||||
break Input
|
||||
}
|
||||
// scanEnd is delayed one byte.
|
||||
// We might block trying to get that byte from src,
|
||||
// so instead invent a space byte.
|
||||
if (v == scanEndObject || v == scanEndArray) && dec.scan.step(&dec.scan, ' ') == scanEnd {
|
||||
scanp += i + 1
|
||||
break Input
|
||||
}
|
||||
if v == scanError {
|
||||
dec.err = dec.scan.err
|
||||
return 0, dec.scan.err
|
||||
}
|
||||
}
|
||||
scanp = len(dec.buf)
|
||||
|
||||
// Did the last read have an error?
|
||||
// Delayed until now to allow buffer scan.
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
if dec.scan.step(&dec.scan, ' ') == scanEnd {
|
||||
break Input
|
||||
}
|
||||
if nonSpace(dec.buf) {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
}
|
||||
dec.err = err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
n := scanp - dec.scanp
|
||||
err = dec.refill()
|
||||
scanp = dec.scanp + n
|
||||
}
|
||||
return scanp - dec.scanp, nil
|
||||
}
|
||||
|
||||
func (dec *Decoder) refill() error {
|
||||
// Make room to read more into the buffer.
|
||||
// First slide down data already consumed.
|
||||
if dec.scanp > 0 {
|
||||
n := copy(dec.buf, dec.buf[dec.scanp:])
|
||||
dec.buf = dec.buf[:n]
|
||||
dec.scanp = 0
|
||||
}
|
||||
|
||||
// Grow buffer if not large enough.
|
||||
const minRead = 512
|
||||
if cap(dec.buf)-len(dec.buf) < minRead {
|
||||
newBuf := make([]byte, len(dec.buf), 2*cap(dec.buf)+minRead)
|
||||
copy(newBuf, dec.buf)
|
||||
dec.buf = newBuf
|
||||
}
|
||||
|
||||
// Read. Delay error for next iteration (after scan).
|
||||
n, err := dec.r.Read(dec.buf[len(dec.buf):cap(dec.buf)])
|
||||
dec.buf = dec.buf[0 : len(dec.buf)+n]
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func nonSpace(b []byte) bool {
|
||||
for _, c := range b {
|
||||
if !isSpace(c) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// An Encoder writes JSON objects to an output stream.
|
||||
type Encoder struct {
|
||||
w io.Writer
|
||||
err error
|
||||
}
|
||||
|
||||
// NewEncoder returns a new encoder that writes to w.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{w: w}
|
||||
}
|
||||
|
||||
// Encode writes the JSON encoding of v to the stream,
|
||||
// followed by a newline character.
|
||||
//
|
||||
// See the documentation for Marshal for details about the
|
||||
// conversion of Go values to JSON.
|
||||
func (enc *Encoder) Encode(v interface{}) error {
|
||||
if enc.err != nil {
|
||||
return enc.err
|
||||
}
|
||||
e := newEncodeState()
|
||||
err := e.marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Terminate each value with a newline.
|
||||
// This makes the output look a little nicer
|
||||
// when debugging, and some kind of space
|
||||
// is required if the encoded value was a number,
|
||||
// so that the reader knows there aren't more
|
||||
// digits coming.
|
||||
e.WriteByte('\n')
|
||||
|
||||
if _, err = enc.w.Write(e.Bytes()); err != nil {
|
||||
enc.err = err
|
||||
}
|
||||
encodeStatePool.Put(e)
|
||||
return err
|
||||
}
|
||||
|
||||
// RawMessage is a raw encoded JSON object.
|
||||
// It implements Marshaler and Unmarshaler and can
|
||||
// be used to delay JSON decoding or precompute a JSON encoding.
|
||||
type RawMessage []byte
|
||||
|
||||
// MarshalJSON returns *m as the JSON encoding of m.
|
||||
func (m *RawMessage) MarshalJSON() ([]byte, error) {
|
||||
return *m, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON sets *m to a copy of data.
|
||||
func (m *RawMessage) UnmarshalJSON(data []byte) error {
|
||||
if m == nil {
|
||||
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
|
||||
}
|
||||
*m = append((*m)[0:0], data...)
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ Marshaler = (*RawMessage)(nil)
|
||||
var _ Unmarshaler = (*RawMessage)(nil)
|
||||
|
||||
// A Token holds a value of one of these types:
|
||||
//
|
||||
// Delim, for the four JSON delimiters [ ] { }
|
||||
// bool, for JSON booleans
|
||||
// float64, for JSON numbers
|
||||
// Number, for JSON numbers
|
||||
// string, for JSON string literals
|
||||
// nil, for JSON null
|
||||
//
|
||||
type Token interface{}
|
||||
|
||||
const (
|
||||
tokenTopValue = iota
|
||||
tokenArrayStart
|
||||
tokenArrayValue
|
||||
tokenArrayComma
|
||||
tokenObjectStart
|
||||
tokenObjectKey
|
||||
tokenObjectColon
|
||||
tokenObjectValue
|
||||
tokenObjectComma
|
||||
)
|
||||
|
||||
// advance tokenstate from a separator state to a value state
|
||||
func (dec *Decoder) tokenPrepareForDecode() error {
|
||||
// Note: Not calling peek before switch, to avoid
|
||||
// putting peek into the standard Decode path.
|
||||
// peek is only called when using the Token API.
|
||||
switch dec.tokenState {
|
||||
case tokenArrayComma:
|
||||
c, err := dec.peek()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c != ',' {
|
||||
return &SyntaxError{"expected comma after array element", 0}
|
||||
}
|
||||
dec.scanp++
|
||||
dec.tokenState = tokenArrayValue
|
||||
case tokenObjectColon:
|
||||
c, err := dec.peek()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c != ':' {
|
||||
return &SyntaxError{"expected colon after object key", 0}
|
||||
}
|
||||
dec.scanp++
|
||||
dec.tokenState = tokenObjectValue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dec *Decoder) tokenValueAllowed() bool {
|
||||
switch dec.tokenState {
|
||||
case tokenTopValue, tokenArrayStart, tokenArrayValue, tokenObjectValue:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (dec *Decoder) tokenValueEnd() {
|
||||
switch dec.tokenState {
|
||||
case tokenArrayStart, tokenArrayValue:
|
||||
dec.tokenState = tokenArrayComma
|
||||
case tokenObjectValue:
|
||||
dec.tokenState = tokenObjectComma
|
||||
}
|
||||
}
|
||||
|
||||
// A Delim is a JSON array or object delimiter, one of [ ] { or }.
|
||||
type Delim rune
|
||||
|
||||
func (d Delim) String() string {
|
||||
return string(d)
|
||||
}
|
||||
|
||||
// Token returns the next JSON token in the input stream.
|
||||
// At the end of the input stream, Token returns nil, io.EOF.
|
||||
//
|
||||
// Token guarantees that the delimiters [ ] { } it returns are
|
||||
// properly nested and matched: if Token encounters an unexpected
|
||||
// delimiter in the input, it will return an error.
|
||||
//
|
||||
// The input stream consists of basic JSON values—bool, string,
|
||||
// number, and null—along with delimiters [ ] { } of type Delim
|
||||
// to mark the start and end of arrays and objects.
|
||||
// Commas and colons are elided.
|
||||
func (dec *Decoder) Token() (Token, error) {
|
||||
for {
|
||||
c, err := dec.peek()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch c {
|
||||
case '[':
|
||||
if !dec.tokenValueAllowed() {
|
||||
return dec.tokenError(c)
|
||||
}
|
||||
dec.scanp++
|
||||
dec.tokenStack = append(dec.tokenStack, dec.tokenState)
|
||||
dec.tokenState = tokenArrayStart
|
||||
return Delim('['), nil
|
||||
|
||||
case ']':
|
||||
if dec.tokenState != tokenArrayStart && dec.tokenState != tokenArrayComma {
|
||||
return dec.tokenError(c)
|
||||
}
|
||||
dec.scanp++
|
||||
dec.tokenState = dec.tokenStack[len(dec.tokenStack)-1]
|
||||
dec.tokenStack = dec.tokenStack[:len(dec.tokenStack)-1]
|
||||
dec.tokenValueEnd()
|
||||
return Delim(']'), nil
|
||||
|
||||
case '{':
|
||||
if !dec.tokenValueAllowed() {
|
||||
return dec.tokenError(c)
|
||||
}
|
||||
dec.scanp++
|
||||
dec.tokenStack = append(dec.tokenStack, dec.tokenState)
|
||||
dec.tokenState = tokenObjectStart
|
||||
return Delim('{'), nil
|
||||
|
||||
case '}':
|
||||
if dec.tokenState != tokenObjectStart && dec.tokenState != tokenObjectComma {
|
||||
return dec.tokenError(c)
|
||||
}
|
||||
dec.scanp++
|
||||
dec.tokenState = dec.tokenStack[len(dec.tokenStack)-1]
|
||||
dec.tokenStack = dec.tokenStack[:len(dec.tokenStack)-1]
|
||||
dec.tokenValueEnd()
|
||||
return Delim('}'), nil
|
||||
|
||||
case ':':
|
||||
if dec.tokenState != tokenObjectColon {
|
||||
return dec.tokenError(c)
|
||||
}
|
||||
dec.scanp++
|
||||
dec.tokenState = tokenObjectValue
|
||||
continue
|
||||
|
||||
case ',':
|
||||
if dec.tokenState == tokenArrayComma {
|
||||
dec.scanp++
|
||||
dec.tokenState = tokenArrayValue
|
||||
continue
|
||||
}
|
||||
if dec.tokenState == tokenObjectComma {
|
||||
dec.scanp++
|
||||
dec.tokenState = tokenObjectKey
|
||||
continue
|
||||
}
|
||||
return dec.tokenError(c)
|
||||
|
||||
case '"':
|
||||
if dec.tokenState == tokenObjectStart || dec.tokenState == tokenObjectKey {
|
||||
var x string
|
||||
old := dec.tokenState
|
||||
dec.tokenState = tokenTopValue
|
||||
err := dec.Decode(&x)
|
||||
dec.tokenState = old
|
||||
if err != nil {
|
||||
clearOffset(err)
|
||||
return nil, err
|
||||
}
|
||||
dec.tokenState = tokenObjectColon
|
||||
return x, nil
|
||||
}
|
||||
fallthrough
|
||||
|
||||
default:
|
||||
if !dec.tokenValueAllowed() {
|
||||
return dec.tokenError(c)
|
||||
}
|
||||
var x interface{}
|
||||
if err := dec.Decode(&x); err != nil {
|
||||
clearOffset(err)
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func clearOffset(err error) {
|
||||
if s, ok := err.(*SyntaxError); ok {
|
||||
s.Offset = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *Decoder) tokenError(c byte) (Token, error) {
|
||||
var context string
|
||||
switch dec.tokenState {
|
||||
case tokenTopValue:
|
||||
context = " looking for beginning of value"
|
||||
case tokenArrayStart, tokenArrayValue, tokenObjectValue:
|
||||
context = " looking for beginning of value"
|
||||
case tokenArrayComma:
|
||||
context = " after array element"
|
||||
case tokenObjectKey:
|
||||
context = " looking for beginning of object key string"
|
||||
case tokenObjectColon:
|
||||
context = " after object key"
|
||||
case tokenObjectComma:
|
||||
context = " after object key:value pair"
|
||||
}
|
||||
return nil, &SyntaxError{"invalid character " + quoteChar(c) + " " + context, 0}
|
||||
}
|
||||
|
||||
// More reports whether there is another element in the
|
||||
// current array or object being parsed.
|
||||
func (dec *Decoder) More() bool {
|
||||
c, err := dec.peek()
|
||||
return err == nil && c != ']' && c != '}'
|
||||
}
|
||||
|
||||
func (dec *Decoder) peek() (byte, error) {
|
||||
var err error
|
||||
for {
|
||||
for i := dec.scanp; i < len(dec.buf); i++ {
|
||||
c := dec.buf[i]
|
||||
if isSpace(c) {
|
||||
continue
|
||||
}
|
||||
dec.scanp = i
|
||||
return c, nil
|
||||
}
|
||||
// buffer has been scanned, now report any error
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = dec.refill()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
TODO
|
||||
|
||||
// EncodeToken writes the given JSON token to the stream.
|
||||
// It returns an error if the delimiters [ ] { } are not properly used.
|
||||
//
|
||||
// EncodeToken does not call Flush, because usually it is part of
|
||||
// a larger operation such as Encode, and those will call Flush when finished.
|
||||
// Callers that create an Encoder and then invoke EncodeToken directly,
|
||||
// without using Encode, need to call Flush when finished to ensure that
|
||||
// the JSON is written to the underlying writer.
|
||||
func (e *Encoder) EncodeToken(t Token) error {
|
||||
...
|
||||
}
|
||||
|
||||
*/
|
||||
44
vendor/gopkg.in/square/go-jose.v2/json/tags.go
generated
vendored
Normal file
44
vendor/gopkg.in/square/go-jose.v2/json/tags.go
generated
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// tagOptions is the string following a comma in a struct field's "json"
|
||||
// tag, or the empty string. It does not include the leading comma.
|
||||
type tagOptions string
|
||||
|
||||
// parseTag splits a struct field's json tag into its name and
|
||||
// comma-separated options.
|
||||
func parseTag(tag string) (string, tagOptions) {
|
||||
if idx := strings.Index(tag, ","); idx != -1 {
|
||||
return tag[:idx], tagOptions(tag[idx+1:])
|
||||
}
|
||||
return tag, tagOptions("")
|
||||
}
|
||||
|
||||
// Contains reports whether a comma-separated list of options
|
||||
// contains a particular substr flag. substr must be surrounded by a
|
||||
// string boundary or commas.
|
||||
func (o tagOptions) Contains(optionName string) bool {
|
||||
if len(o) == 0 {
|
||||
return false
|
||||
}
|
||||
s := string(o)
|
||||
for s != "" {
|
||||
var next string
|
||||
i := strings.Index(s, ",")
|
||||
if i >= 0 {
|
||||
s, next = s[:i], s[i+1:]
|
||||
}
|
||||
if s == optionName {
|
||||
return true
|
||||
}
|
||||
s = next
|
||||
}
|
||||
return false
|
||||
}
|
||||
291
vendor/gopkg.in/square/go-jose.v2/jwe.go
generated
vendored
Normal file
291
vendor/gopkg.in/square/go-jose.v2/jwe.go
generated
vendored
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
/*-
|
||||
* Copyright 2014 Square Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jose
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// rawJSONWebEncryption represents a raw JWE JSON object. Used for parsing/serializing.
|
||||
type rawJSONWebEncryption struct {
|
||||
Protected *byteBuffer `json:"protected,omitempty"`
|
||||
Unprotected *rawHeader `json:"unprotected,omitempty"`
|
||||
Header *rawHeader `json:"header,omitempty"`
|
||||
Recipients []rawRecipientInfo `json:"recipients,omitempty"`
|
||||
Aad *byteBuffer `json:"aad,omitempty"`
|
||||
EncryptedKey *byteBuffer `json:"encrypted_key,omitempty"`
|
||||
Iv *byteBuffer `json:"iv,omitempty"`
|
||||
Ciphertext *byteBuffer `json:"ciphertext,omitempty"`
|
||||
Tag *byteBuffer `json:"tag,omitempty"`
|
||||
}
|
||||
|
||||
// rawRecipientInfo represents a raw JWE Per-Recipient header JSON object. Used for parsing/serializing.
|
||||
type rawRecipientInfo struct {
|
||||
Header *rawHeader `json:"header,omitempty"`
|
||||
EncryptedKey string `json:"encrypted_key,omitempty"`
|
||||
}
|
||||
|
||||
// JSONWebEncryption represents an encrypted JWE object after parsing.
|
||||
type JSONWebEncryption struct {
|
||||
Header Header
|
||||
protected, unprotected *rawHeader
|
||||
recipients []recipientInfo
|
||||
aad, iv, ciphertext, tag []byte
|
||||
original *rawJSONWebEncryption
|
||||
}
|
||||
|
||||
// recipientInfo represents a raw JWE Per-Recipient header JSON object after parsing.
|
||||
type recipientInfo struct {
|
||||
header *rawHeader
|
||||
encryptedKey []byte
|
||||
}
|
||||
|
||||
// GetAuthData retrieves the (optional) authenticated data attached to the object.
|
||||
func (obj JSONWebEncryption) GetAuthData() []byte {
|
||||
if obj.aad != nil {
|
||||
out := make([]byte, len(obj.aad))
|
||||
copy(out, obj.aad)
|
||||
return out
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the merged header values
|
||||
func (obj JSONWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader {
|
||||
out := rawHeader{}
|
||||
out.merge(obj.protected)
|
||||
out.merge(obj.unprotected)
|
||||
|
||||
if recipient != nil {
|
||||
out.merge(recipient.header)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// Get the additional authenticated data from a JWE object.
|
||||
func (obj JSONWebEncryption) computeAuthData() []byte {
|
||||
var protected string
|
||||
|
||||
if obj.original != nil {
|
||||
protected = obj.original.Protected.base64()
|
||||
} else {
|
||||
protected = base64.RawURLEncoding.EncodeToString(mustSerializeJSON((obj.protected)))
|
||||
}
|
||||
|
||||
output := []byte(protected)
|
||||
if obj.aad != nil {
|
||||
output = append(output, '.')
|
||||
output = append(output, []byte(base64.RawURLEncoding.EncodeToString(obj.aad))...)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// ParseEncrypted parses an encrypted message in compact or full serialization format.
|
||||
func ParseEncrypted(input string) (*JSONWebEncryption, error) {
|
||||
input = stripWhitespace(input)
|
||||
if strings.HasPrefix(input, "{") {
|
||||
return parseEncryptedFull(input)
|
||||
}
|
||||
|
||||
return parseEncryptedCompact(input)
|
||||
}
|
||||
|
||||
// parseEncryptedFull parses a message in compact format.
|
||||
func parseEncryptedFull(input string) (*JSONWebEncryption, error) {
|
||||
var parsed rawJSONWebEncryption
|
||||
err := json.Unmarshal([]byte(input), &parsed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parsed.sanitized()
|
||||
}
|
||||
|
||||
// sanitized produces a cleaned-up JWE object from the raw JSON.
|
||||
func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
|
||||
obj := &JSONWebEncryption{
|
||||
original: parsed,
|
||||
unprotected: parsed.Unprotected,
|
||||
}
|
||||
|
||||
// Check that there is not a nonce in the unprotected headers
|
||||
if parsed.Unprotected != nil {
|
||||
if nonce := parsed.Unprotected.getNonce(); nonce != "" {
|
||||
return nil, ErrUnprotectedNonce
|
||||
}
|
||||
}
|
||||
if parsed.Header != nil {
|
||||
if nonce := parsed.Header.getNonce(); nonce != "" {
|
||||
return nil, ErrUnprotectedNonce
|
||||
}
|
||||
}
|
||||
|
||||
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
|
||||
err := json.Unmarshal(parsed.Protected.bytes(), &obj.protected)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid protected header: %s, %s", err, parsed.Protected.base64())
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this must be called _after_ we parse the protected header,
|
||||
// otherwise fields from the protected header will not get picked up.
|
||||
var err error
|
||||
mergedHeaders := obj.mergedHeaders(nil)
|
||||
obj.Header, err = mergedHeaders.sanitized()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("square/go-jose: cannot sanitize merged headers: %v (%v)", err, mergedHeaders)
|
||||
}
|
||||
|
||||
if len(parsed.Recipients) == 0 {
|
||||
obj.recipients = []recipientInfo{
|
||||
{
|
||||
header: parsed.Header,
|
||||
encryptedKey: parsed.EncryptedKey.bytes(),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
obj.recipients = make([]recipientInfo, len(parsed.Recipients))
|
||||
for r := range parsed.Recipients {
|
||||
encryptedKey, err := base64.RawURLEncoding.DecodeString(parsed.Recipients[r].EncryptedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check that there is not a nonce in the unprotected header
|
||||
if parsed.Recipients[r].Header != nil && parsed.Recipients[r].Header.getNonce() != "" {
|
||||
return nil, ErrUnprotectedNonce
|
||||
}
|
||||
|
||||
obj.recipients[r].header = parsed.Recipients[r].Header
|
||||
obj.recipients[r].encryptedKey = encryptedKey
|
||||
}
|
||||
}
|
||||
|
||||
for _, recipient := range obj.recipients {
|
||||
headers := obj.mergedHeaders(&recipient)
|
||||
if headers.getAlgorithm() == "" || headers.getEncryption() == "" {
|
||||
return nil, fmt.Errorf("square/go-jose: message is missing alg/enc headers")
|
||||
}
|
||||
}
|
||||
|
||||
obj.iv = parsed.Iv.bytes()
|
||||
obj.ciphertext = parsed.Ciphertext.bytes()
|
||||
obj.tag = parsed.Tag.bytes()
|
||||
obj.aad = parsed.Aad.bytes()
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// parseEncryptedCompact parses a message in compact format.
|
||||
func parseEncryptedCompact(input string) (*JSONWebEncryption, error) {
|
||||
parts := strings.Split(input, ".")
|
||||
if len(parts) != 5 {
|
||||
return nil, fmt.Errorf("square/go-jose: compact JWE format must have five parts")
|
||||
}
|
||||
|
||||
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encryptedKey, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iv, err := base64.RawURLEncoding.DecodeString(parts[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext, err := base64.RawURLEncoding.DecodeString(parts[3])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag, err := base64.RawURLEncoding.DecodeString(parts[4])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw := &rawJSONWebEncryption{
|
||||
Protected: newBuffer(rawProtected),
|
||||
EncryptedKey: newBuffer(encryptedKey),
|
||||
Iv: newBuffer(iv),
|
||||
Ciphertext: newBuffer(ciphertext),
|
||||
Tag: newBuffer(tag),
|
||||
}
|
||||
|
||||
return raw.sanitized()
|
||||
}
|
||||
|
||||
// CompactSerialize serializes an object using the compact serialization format.
|
||||
func (obj JSONWebEncryption) CompactSerialize() (string, error) {
|
||||
if len(obj.recipients) != 1 || obj.unprotected != nil ||
|
||||
obj.protected == nil || obj.recipients[0].header != nil {
|
||||
return "", ErrNotSupported
|
||||
}
|
||||
|
||||
serializedProtected := mustSerializeJSON(obj.protected)
|
||||
|
||||
return fmt.Sprintf(
|
||||
"%s.%s.%s.%s.%s",
|
||||
base64.RawURLEncoding.EncodeToString(serializedProtected),
|
||||
base64.RawURLEncoding.EncodeToString(obj.recipients[0].encryptedKey),
|
||||
base64.RawURLEncoding.EncodeToString(obj.iv),
|
||||
base64.RawURLEncoding.EncodeToString(obj.ciphertext),
|
||||
base64.RawURLEncoding.EncodeToString(obj.tag)), nil
|
||||
}
|
||||
|
||||
// FullSerialize serializes an object using the full JSON serialization format.
|
||||
func (obj JSONWebEncryption) FullSerialize() string {
|
||||
raw := rawJSONWebEncryption{
|
||||
Unprotected: obj.unprotected,
|
||||
Iv: newBuffer(obj.iv),
|
||||
Ciphertext: newBuffer(obj.ciphertext),
|
||||
EncryptedKey: newBuffer(obj.recipients[0].encryptedKey),
|
||||
Tag: newBuffer(obj.tag),
|
||||
Aad: newBuffer(obj.aad),
|
||||
Recipients: []rawRecipientInfo{},
|
||||
}
|
||||
|
||||
if len(obj.recipients) > 1 {
|
||||
for _, recipient := range obj.recipients {
|
||||
info := rawRecipientInfo{
|
||||
Header: recipient.header,
|
||||
EncryptedKey: base64.RawURLEncoding.EncodeToString(recipient.encryptedKey),
|
||||
}
|
||||
raw.Recipients = append(raw.Recipients, info)
|
||||
}
|
||||
} else {
|
||||
// Use flattened serialization
|
||||
raw.Header = obj.recipients[0].header
|
||||
raw.EncryptedKey = newBuffer(obj.recipients[0].encryptedKey)
|
||||
}
|
||||
|
||||
if obj.protected != nil {
|
||||
raw.Protected = newBuffer(mustSerializeJSON(obj.protected))
|
||||
}
|
||||
|
||||
return string(mustSerializeJSON(raw))
|
||||
}
|
||||
537
vendor/gopkg.in/square/go-jose.v2/jwk.go
generated
vendored
Normal file
537
vendor/gopkg.in/square/go-jose.v2/jwk.go
generated
vendored
Normal file
|
|
@ -0,0 +1,537 @@
|
|||
/*-
|
||||
* Copyright 2014 Square Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jose
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
|
||||
"gopkg.in/square/go-jose.v2/json"
|
||||
)
|
||||
|
||||
// rawJSONWebKey represents a public or private key in JWK format, used for parsing/serializing.
|
||||
type rawJSONWebKey struct {
|
||||
Use string `json:"use,omitempty"`
|
||||
Kty string `json:"kty,omitempty"`
|
||||
Kid string `json:"kid,omitempty"`
|
||||
Crv string `json:"crv,omitempty"`
|
||||
Alg string `json:"alg,omitempty"`
|
||||
K *byteBuffer `json:"k,omitempty"`
|
||||
X *byteBuffer `json:"x,omitempty"`
|
||||
Y *byteBuffer `json:"y,omitempty"`
|
||||
N *byteBuffer `json:"n,omitempty"`
|
||||
E *byteBuffer `json:"e,omitempty"`
|
||||
// -- Following fields are only used for private keys --
|
||||
// RSA uses D, P and Q, while ECDSA uses only D. Fields Dp, Dq, and Qi are
|
||||
// completely optional. Therefore for RSA/ECDSA, D != nil is a contract that
|
||||
// we have a private key whereas D == nil means we have only a public key.
|
||||
D *byteBuffer `json:"d,omitempty"`
|
||||
P *byteBuffer `json:"p,omitempty"`
|
||||
Q *byteBuffer `json:"q,omitempty"`
|
||||
Dp *byteBuffer `json:"dp,omitempty"`
|
||||
Dq *byteBuffer `json:"dq,omitempty"`
|
||||
Qi *byteBuffer `json:"qi,omitempty"`
|
||||
// Certificates
|
||||
X5c []string `json:"x5c,omitempty"`
|
||||
}
|
||||
|
||||
// JSONWebKey represents a public or private key in JWK format.
|
||||
type JSONWebKey struct {
|
||||
Key interface{}
|
||||
Certificates []*x509.Certificate
|
||||
KeyID string
|
||||
Algorithm string
|
||||
Use string
|
||||
}
|
||||
|
||||
// MarshalJSON serializes the given key to its JSON representation.
|
||||
func (k JSONWebKey) MarshalJSON() ([]byte, error) {
|
||||
var raw *rawJSONWebKey
|
||||
var err error
|
||||
|
||||
switch key := k.Key.(type) {
|
||||
case ed25519.PublicKey:
|
||||
raw = fromEdPublicKey(key)
|
||||
case *ecdsa.PublicKey:
|
||||
raw, err = fromEcPublicKey(key)
|
||||
case *rsa.PublicKey:
|
||||
raw = fromRsaPublicKey(key)
|
||||
case ed25519.PrivateKey:
|
||||
raw, err = fromEdPrivateKey(key)
|
||||
case *ecdsa.PrivateKey:
|
||||
raw, err = fromEcPrivateKey(key)
|
||||
case *rsa.PrivateKey:
|
||||
raw, err = fromRsaPrivateKey(key)
|
||||
case []byte:
|
||||
raw, err = fromSymmetricKey(key)
|
||||
default:
|
||||
return nil, fmt.Errorf("square/go-jose: unknown key type '%s'", reflect.TypeOf(key))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw.Kid = k.KeyID
|
||||
raw.Alg = k.Algorithm
|
||||
raw.Use = k.Use
|
||||
|
||||
for _, cert := range k.Certificates {
|
||||
raw.X5c = append(raw.X5c, base64.StdEncoding.EncodeToString(cert.Raw))
|
||||
}
|
||||
|
||||
return json.Marshal(raw)
|
||||
}
|
||||
|
||||
// UnmarshalJSON reads a key from its JSON representation.
|
||||
func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
|
||||
var raw rawJSONWebKey
|
||||
err = json.Unmarshal(data, &raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var key interface{}
|
||||
switch raw.Kty {
|
||||
case "EC":
|
||||
if raw.D != nil {
|
||||
key, err = raw.ecPrivateKey()
|
||||
} else {
|
||||
key, err = raw.ecPublicKey()
|
||||
}
|
||||
case "RSA":
|
||||
if raw.D != nil {
|
||||
key, err = raw.rsaPrivateKey()
|
||||
} else {
|
||||
key, err = raw.rsaPublicKey()
|
||||
}
|
||||
case "oct":
|
||||
key, err = raw.symmetricKey()
|
||||
case "OKP":
|
||||
if raw.Crv == "Ed25519" && raw.X != nil {
|
||||
if raw.D != nil {
|
||||
key, err = raw.edPrivateKey()
|
||||
} else {
|
||||
key, err = raw.edPublicKey()
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("square/go-jose: unknown curve %s'", raw.Crv)
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("square/go-jose: unknown json web key type '%s'", raw.Kty)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
*k = JSONWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use}
|
||||
}
|
||||
|
||||
k.Certificates = make([]*x509.Certificate, len(raw.X5c))
|
||||
for i, cert := range raw.X5c {
|
||||
raw, err := base64.StdEncoding.DecodeString(cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
k.Certificates[i], err = x509.ParseCertificate(raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// JSONWebKeySet represents a JWK Set object.
|
||||
type JSONWebKeySet struct {
|
||||
Keys []JSONWebKey `json:"keys"`
|
||||
}
|
||||
|
||||
// Key convenience method returns keys by key ID. Specification states
|
||||
// that a JWK Set "SHOULD" use distinct key IDs, but allows for some
|
||||
// cases where they are not distinct. Hence method returns a slice
|
||||
// of JSONWebKeys.
|
||||
func (s *JSONWebKeySet) Key(kid string) []JSONWebKey {
|
||||
var keys []JSONWebKey
|
||||
for _, key := range s.Keys {
|
||||
if key.KeyID == kid {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
const rsaThumbprintTemplate = `{"e":"%s","kty":"RSA","n":"%s"}`
|
||||
const ecThumbprintTemplate = `{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`
|
||||
const edThumbprintTemplate = `{"crv":"%s","kty":"OKP",x":"%s"}`
|
||||
|
||||
func ecThumbprintInput(curve elliptic.Curve, x, y *big.Int) (string, error) {
|
||||
coordLength := curveSize(curve)
|
||||
crv, err := curveName(curve)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf(ecThumbprintTemplate, crv,
|
||||
newFixedSizeBuffer(x.Bytes(), coordLength).base64(),
|
||||
newFixedSizeBuffer(y.Bytes(), coordLength).base64()), nil
|
||||
}
|
||||
|
||||
func rsaThumbprintInput(n *big.Int, e int) (string, error) {
|
||||
return fmt.Sprintf(rsaThumbprintTemplate,
|
||||
newBufferFromInt(uint64(e)).base64(),
|
||||
newBuffer(n.Bytes()).base64()), nil
|
||||
}
|
||||
|
||||
func edThumbprintInput(ed ed25519.PublicKey) (string, error) {
|
||||
crv := "Ed25519"
|
||||
return fmt.Sprintf(edThumbprintTemplate, crv,
|
||||
newFixedSizeBuffer(ed, 32).base64()), nil
|
||||
}
|
||||
|
||||
// Thumbprint computes the JWK Thumbprint of a key using the
|
||||
// indicated hash algorithm.
|
||||
func (k *JSONWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
|
||||
var input string
|
||||
var err error
|
||||
switch key := k.Key.(type) {
|
||||
case ed25519.PublicKey:
|
||||
input, err = edThumbprintInput(key)
|
||||
case *ecdsa.PublicKey:
|
||||
input, err = ecThumbprintInput(key.Curve, key.X, key.Y)
|
||||
case *ecdsa.PrivateKey:
|
||||
input, err = ecThumbprintInput(key.Curve, key.X, key.Y)
|
||||
case *rsa.PublicKey:
|
||||
input, err = rsaThumbprintInput(key.N, key.E)
|
||||
case *rsa.PrivateKey:
|
||||
input, err = rsaThumbprintInput(key.N, key.E)
|
||||
case ed25519.PrivateKey:
|
||||
input, err = edThumbprintInput(ed25519.PublicKey(key[0:32]))
|
||||
default:
|
||||
return nil, fmt.Errorf("square/go-jose: unknown key type '%s'", reflect.TypeOf(key))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := hash.New()
|
||||
h.Write([]byte(input))
|
||||
return h.Sum(nil), nil
|
||||
}
|
||||
|
||||
// IsPublic returns true if the JWK represents a public key (not symmetric, not private).
|
||||
func (k *JSONWebKey) IsPublic() bool {
|
||||
switch k.Key.(type) {
|
||||
case *ecdsa.PublicKey, *rsa.PublicKey, *ed25519.PublicKey:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Valid checks that the key contains the expected parameters.
|
||||
func (k *JSONWebKey) Valid() bool {
|
||||
if k.Key == nil {
|
||||
return false
|
||||
}
|
||||
switch key := k.Key.(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
if key.Curve == nil || key.X == nil || key.Y == nil {
|
||||
return false
|
||||
}
|
||||
case *ecdsa.PrivateKey:
|
||||
if key.Curve == nil || key.X == nil || key.Y == nil || key.D == nil {
|
||||
return false
|
||||
}
|
||||
case *rsa.PublicKey:
|
||||
if key.N == nil || key.E == 0 {
|
||||
return false
|
||||
}
|
||||
case *rsa.PrivateKey:
|
||||
if key.N == nil || key.E == 0 || key.D == nil || len(key.Primes) < 2 {
|
||||
return false
|
||||
}
|
||||
case *ed25519.PublicKey:
|
||||
if len(*key) != 32 {
|
||||
return false
|
||||
}
|
||||
case *ed25519.PrivateKey:
|
||||
if len(*key) != 64 {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (key rawJSONWebKey) rsaPublicKey() (*rsa.PublicKey, error) {
|
||||
if key.N == nil || key.E == nil {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid RSA key, missing n/e values")
|
||||
}
|
||||
|
||||
return &rsa.PublicKey{
|
||||
N: key.N.bigInt(),
|
||||
E: key.E.toInt(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func fromEdPublicKey(pub ed25519.PublicKey) *rawJSONWebKey {
|
||||
return &rawJSONWebKey{
|
||||
Kty: "OKP",
|
||||
Crv: "Ed25519",
|
||||
X: newBuffer(pub),
|
||||
}
|
||||
}
|
||||
|
||||
func fromRsaPublicKey(pub *rsa.PublicKey) *rawJSONWebKey {
|
||||
return &rawJSONWebKey{
|
||||
Kty: "RSA",
|
||||
N: newBuffer(pub.N.Bytes()),
|
||||
E: newBufferFromInt(uint64(pub.E)),
|
||||
}
|
||||
}
|
||||
|
||||
func (key rawJSONWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
|
||||
var curve elliptic.Curve
|
||||
switch key.Crv {
|
||||
case "P-256":
|
||||
curve = elliptic.P256()
|
||||
case "P-384":
|
||||
curve = elliptic.P384()
|
||||
case "P-521":
|
||||
curve = elliptic.P521()
|
||||
default:
|
||||
return nil, fmt.Errorf("square/go-jose: unsupported elliptic curve '%s'", key.Crv)
|
||||
}
|
||||
|
||||
if key.X == nil || key.Y == nil {
|
||||
return nil, errors.New("square/go-jose: invalid EC key, missing x/y values")
|
||||
}
|
||||
|
||||
x := key.X.bigInt()
|
||||
y := key.Y.bigInt()
|
||||
|
||||
if !curve.IsOnCurve(x, y) {
|
||||
return nil, errors.New("square/go-jose: invalid EC key, X/Y are not on declared curve")
|
||||
}
|
||||
|
||||
return &ecdsa.PublicKey{
|
||||
Curve: curve,
|
||||
X: x,
|
||||
Y: y,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func fromEcPublicKey(pub *ecdsa.PublicKey) (*rawJSONWebKey, error) {
|
||||
if pub == nil || pub.X == nil || pub.Y == nil {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid EC key (nil, or X/Y missing)")
|
||||
}
|
||||
|
||||
name, err := curveName(pub.Curve)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
size := curveSize(pub.Curve)
|
||||
|
||||
xBytes := pub.X.Bytes()
|
||||
yBytes := pub.Y.Bytes()
|
||||
|
||||
if len(xBytes) > size || len(yBytes) > size {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid EC key (X/Y too large)")
|
||||
}
|
||||
|
||||
key := &rawJSONWebKey{
|
||||
Kty: "EC",
|
||||
Crv: name,
|
||||
X: newFixedSizeBuffer(xBytes, size),
|
||||
Y: newFixedSizeBuffer(yBytes, size),
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (key rawJSONWebKey) edPrivateKey() (ed25519.PrivateKey, error) {
|
||||
var missing []string
|
||||
switch {
|
||||
case key.D == nil:
|
||||
missing = append(missing, "D")
|
||||
case key.X == nil:
|
||||
missing = append(missing, "X")
|
||||
}
|
||||
|
||||
if len(missing) > 0 {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid Ed25519 private key, missing %s value(s)", strings.Join(missing, ", "))
|
||||
}
|
||||
|
||||
privateKey := make([]byte, ed25519.PrivateKeySize)
|
||||
copy(privateKey[0:32], key.X.bytes())
|
||||
copy(privateKey[32:], key.D.bytes())
|
||||
rv := ed25519.PrivateKey(privateKey)
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (key rawJSONWebKey) edPublicKey() (ed25519.PublicKey, error) {
|
||||
if key.X == nil {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid Ed key, missing x value")
|
||||
}
|
||||
publicKey := make([]byte, ed25519.PublicKeySize)
|
||||
copy(publicKey[0:32], key.X.bytes())
|
||||
rv := ed25519.PublicKey(publicKey)
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
|
||||
var missing []string
|
||||
switch {
|
||||
case key.N == nil:
|
||||
missing = append(missing, "N")
|
||||
case key.E == nil:
|
||||
missing = append(missing, "E")
|
||||
case key.D == nil:
|
||||
missing = append(missing, "D")
|
||||
case key.P == nil:
|
||||
missing = append(missing, "P")
|
||||
case key.Q == nil:
|
||||
missing = append(missing, "Q")
|
||||
}
|
||||
|
||||
if len(missing) > 0 {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid RSA private key, missing %s value(s)", strings.Join(missing, ", "))
|
||||
}
|
||||
|
||||
rv := &rsa.PrivateKey{
|
||||
PublicKey: rsa.PublicKey{
|
||||
N: key.N.bigInt(),
|
||||
E: key.E.toInt(),
|
||||
},
|
||||
D: key.D.bigInt(),
|
||||
Primes: []*big.Int{
|
||||
key.P.bigInt(),
|
||||
key.Q.bigInt(),
|
||||
},
|
||||
}
|
||||
|
||||
if key.Dp != nil {
|
||||
rv.Precomputed.Dp = key.Dp.bigInt()
|
||||
}
|
||||
if key.Dq != nil {
|
||||
rv.Precomputed.Dq = key.Dq.bigInt()
|
||||
}
|
||||
if key.Qi != nil {
|
||||
rv.Precomputed.Qinv = key.Qi.bigInt()
|
||||
}
|
||||
|
||||
err := rv.Validate()
|
||||
return rv, err
|
||||
}
|
||||
|
||||
func fromEdPrivateKey(ed ed25519.PrivateKey) (*rawJSONWebKey, error) {
|
||||
raw := fromEdPublicKey(ed25519.PublicKey(ed[0:32]))
|
||||
|
||||
raw.D = newBuffer(ed[32:])
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func fromRsaPrivateKey(rsa *rsa.PrivateKey) (*rawJSONWebKey, error) {
|
||||
if len(rsa.Primes) != 2 {
|
||||
return nil, ErrUnsupportedKeyType
|
||||
}
|
||||
|
||||
raw := fromRsaPublicKey(&rsa.PublicKey)
|
||||
|
||||
raw.D = newBuffer(rsa.D.Bytes())
|
||||
raw.P = newBuffer(rsa.Primes[0].Bytes())
|
||||
raw.Q = newBuffer(rsa.Primes[1].Bytes())
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func (key rawJSONWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) {
|
||||
var curve elliptic.Curve
|
||||
switch key.Crv {
|
||||
case "P-256":
|
||||
curve = elliptic.P256()
|
||||
case "P-384":
|
||||
curve = elliptic.P384()
|
||||
case "P-521":
|
||||
curve = elliptic.P521()
|
||||
default:
|
||||
return nil, fmt.Errorf("square/go-jose: unsupported elliptic curve '%s'", key.Crv)
|
||||
}
|
||||
|
||||
if key.X == nil || key.Y == nil || key.D == nil {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid EC private key, missing x/y/d values")
|
||||
}
|
||||
|
||||
x := key.X.bigInt()
|
||||
y := key.Y.bigInt()
|
||||
|
||||
if !curve.IsOnCurve(x, y) {
|
||||
return nil, errors.New("square/go-jose: invalid EC key, X/Y are not on declared curve")
|
||||
}
|
||||
|
||||
return &ecdsa.PrivateKey{
|
||||
PublicKey: ecdsa.PublicKey{
|
||||
Curve: curve,
|
||||
X: x,
|
||||
Y: y,
|
||||
},
|
||||
D: key.D.bigInt(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func fromEcPrivateKey(ec *ecdsa.PrivateKey) (*rawJSONWebKey, error) {
|
||||
raw, err := fromEcPublicKey(&ec.PublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ec.D == nil {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid EC private key")
|
||||
}
|
||||
|
||||
raw.D = newBuffer(ec.D.Bytes())
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func fromSymmetricKey(key []byte) (*rawJSONWebKey, error) {
|
||||
return &rawJSONWebKey{
|
||||
Kty: "oct",
|
||||
K: newBuffer(key),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (key rawJSONWebKey) symmetricKey() ([]byte, error) {
|
||||
if key.K == nil {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid OCT (symmetric) key, missing k value")
|
||||
}
|
||||
return key.K.bytes(), nil
|
||||
}
|
||||
282
vendor/gopkg.in/square/go-jose.v2/jws.go
generated
vendored
Normal file
282
vendor/gopkg.in/square/go-jose.v2/jws.go
generated
vendored
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
/*-
|
||||
* Copyright 2014 Square Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jose
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/square/go-jose.v2/json"
|
||||
)
|
||||
|
||||
// rawJSONWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
|
||||
type rawJSONWebSignature struct {
|
||||
Payload *byteBuffer `json:"payload,omitempty"`
|
||||
Signatures []rawSignatureInfo `json:"signatures,omitempty"`
|
||||
Protected *byteBuffer `json:"protected,omitempty"`
|
||||
Header *rawHeader `json:"header,omitempty"`
|
||||
Signature *byteBuffer `json:"signature,omitempty"`
|
||||
}
|
||||
|
||||
// rawSignatureInfo represents a single JWS signature over the JWS payload and protected header.
|
||||
type rawSignatureInfo struct {
|
||||
Protected *byteBuffer `json:"protected,omitempty"`
|
||||
Header *rawHeader `json:"header,omitempty"`
|
||||
Signature *byteBuffer `json:"signature,omitempty"`
|
||||
}
|
||||
|
||||
// JSONWebSignature represents a signed JWS object after parsing.
|
||||
type JSONWebSignature struct {
|
||||
payload []byte
|
||||
// Signatures attached to this object (may be more than one for multi-sig).
|
||||
// Be careful about accessing these directly, prefer to use Verify() or
|
||||
// VerifyMulti() to ensure that the data you're getting is verified.
|
||||
Signatures []Signature
|
||||
}
|
||||
|
||||
// Signature represents a single signature over the JWS payload and protected header.
|
||||
type Signature struct {
|
||||
// Header fields, such as the signature algorithm
|
||||
Header Header
|
||||
|
||||
// The actual signature value
|
||||
Signature []byte
|
||||
|
||||
protected *rawHeader
|
||||
header *rawHeader
|
||||
original *rawSignatureInfo
|
||||
}
|
||||
|
||||
// ParseSigned parses a signed message in compact or full serialization format.
|
||||
func ParseSigned(input string) (*JSONWebSignature, error) {
|
||||
input = stripWhitespace(input)
|
||||
if strings.HasPrefix(input, "{") {
|
||||
return parseSignedFull(input)
|
||||
}
|
||||
|
||||
return parseSignedCompact(input)
|
||||
}
|
||||
|
||||
// Get a header value
|
||||
func (sig Signature) mergedHeaders() rawHeader {
|
||||
out := rawHeader{}
|
||||
out.merge(sig.protected)
|
||||
out.merge(sig.header)
|
||||
return out
|
||||
}
|
||||
|
||||
// Compute data to be signed
|
||||
func (obj JSONWebSignature) computeAuthData(signature *Signature) []byte {
|
||||
var serializedProtected string
|
||||
|
||||
if signature.original != nil && signature.original.Protected != nil {
|
||||
serializedProtected = signature.original.Protected.base64()
|
||||
} else if signature.protected != nil {
|
||||
serializedProtected = base64.RawURLEncoding.EncodeToString(mustSerializeJSON(signature.protected))
|
||||
} else {
|
||||
serializedProtected = ""
|
||||
}
|
||||
|
||||
return []byte(fmt.Sprintf("%s.%s",
|
||||
serializedProtected,
|
||||
base64.RawURLEncoding.EncodeToString(obj.payload)))
|
||||
}
|
||||
|
||||
// parseSignedFull parses a message in full format.
|
||||
func parseSignedFull(input string) (*JSONWebSignature, error) {
|
||||
var parsed rawJSONWebSignature
|
||||
err := json.Unmarshal([]byte(input), &parsed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parsed.sanitized()
|
||||
}
|
||||
|
||||
// sanitized produces a cleaned-up JWS object from the raw JSON.
|
||||
func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
|
||||
if parsed.Payload == nil {
|
||||
return nil, fmt.Errorf("square/go-jose: missing payload in JWS message")
|
||||
}
|
||||
|
||||
obj := &JSONWebSignature{
|
||||
payload: parsed.Payload.bytes(),
|
||||
Signatures: make([]Signature, len(parsed.Signatures)),
|
||||
}
|
||||
|
||||
if len(parsed.Signatures) == 0 {
|
||||
// No signatures array, must be flattened serialization
|
||||
signature := Signature{}
|
||||
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
|
||||
signature.protected = &rawHeader{}
|
||||
err := json.Unmarshal(parsed.Protected.bytes(), signature.protected)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check that there is not a nonce in the unprotected header
|
||||
if parsed.Header != nil && parsed.Header.getNonce() != "" {
|
||||
return nil, ErrUnprotectedNonce
|
||||
}
|
||||
|
||||
signature.header = parsed.Header
|
||||
signature.Signature = parsed.Signature.bytes()
|
||||
// Make a fake "original" rawSignatureInfo to store the unprocessed
|
||||
// Protected header. This is necessary because the Protected header can
|
||||
// contain arbitrary fields not registered as part of the spec. See
|
||||
// https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-4
|
||||
// If we unmarshal Protected into a rawHeader with its explicit list of fields,
|
||||
// we cannot marshal losslessly. So we have to keep around the original bytes.
|
||||
// This is used in computeAuthData, which will first attempt to use
|
||||
// the original bytes of a protected header, and fall back on marshaling the
|
||||
// header struct only if those bytes are not available.
|
||||
signature.original = &rawSignatureInfo{
|
||||
Protected: parsed.Protected,
|
||||
Header: parsed.Header,
|
||||
Signature: parsed.Signature,
|
||||
}
|
||||
|
||||
var err error
|
||||
signature.Header, err = signature.mergedHeaders().sanitized()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
|
||||
jwk := signature.Header.JSONWebKey
|
||||
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
|
||||
return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key")
|
||||
}
|
||||
|
||||
obj.Signatures = append(obj.Signatures, signature)
|
||||
}
|
||||
|
||||
for i, sig := range parsed.Signatures {
|
||||
if sig.Protected != nil && len(sig.Protected.bytes()) > 0 {
|
||||
obj.Signatures[i].protected = &rawHeader{}
|
||||
err := json.Unmarshal(sig.Protected.bytes(), obj.Signatures[i].protected)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check that there is not a nonce in the unprotected header
|
||||
if sig.Header != nil && sig.Header.getNonce() != "" {
|
||||
return nil, ErrUnprotectedNonce
|
||||
}
|
||||
|
||||
var err error
|
||||
obj.Signatures[i].Header, err = obj.Signatures[i].mergedHeaders().sanitized()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj.Signatures[i].Signature = sig.Signature.bytes()
|
||||
|
||||
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
|
||||
jwk := obj.Signatures[i].Header.JSONWebKey
|
||||
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
|
||||
return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key")
|
||||
}
|
||||
|
||||
// Copy value of sig
|
||||
original := sig
|
||||
|
||||
obj.Signatures[i].header = sig.Header
|
||||
obj.Signatures[i].original = &original
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// parseSignedCompact parses a message in compact format.
|
||||
func parseSignedCompact(input string) (*JSONWebSignature, error) {
|
||||
parts := strings.Split(input, ".")
|
||||
if len(parts) != 3 {
|
||||
return nil, fmt.Errorf("square/go-jose: compact JWS format must have three parts")
|
||||
}
|
||||
|
||||
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := base64.RawURLEncoding.DecodeString(parts[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw := &rawJSONWebSignature{
|
||||
Payload: newBuffer(payload),
|
||||
Protected: newBuffer(rawProtected),
|
||||
Signature: newBuffer(signature),
|
||||
}
|
||||
return raw.sanitized()
|
||||
}
|
||||
|
||||
// CompactSerialize serializes an object using the compact serialization format.
|
||||
func (obj JSONWebSignature) CompactSerialize() (string, error) {
|
||||
if len(obj.Signatures) != 1 || obj.Signatures[0].header != nil || obj.Signatures[0].protected == nil {
|
||||
return "", ErrNotSupported
|
||||
}
|
||||
|
||||
serializedProtected := mustSerializeJSON(obj.Signatures[0].protected)
|
||||
|
||||
return fmt.Sprintf(
|
||||
"%s.%s.%s",
|
||||
base64.RawURLEncoding.EncodeToString(serializedProtected),
|
||||
base64.RawURLEncoding.EncodeToString(obj.payload),
|
||||
base64.RawURLEncoding.EncodeToString(obj.Signatures[0].Signature)), nil
|
||||
}
|
||||
|
||||
// FullSerialize serializes an object using the full JSON serialization format.
|
||||
func (obj JSONWebSignature) FullSerialize() string {
|
||||
raw := rawJSONWebSignature{
|
||||
Payload: newBuffer(obj.payload),
|
||||
}
|
||||
|
||||
if len(obj.Signatures) == 1 {
|
||||
if obj.Signatures[0].protected != nil {
|
||||
serializedProtected := mustSerializeJSON(obj.Signatures[0].protected)
|
||||
raw.Protected = newBuffer(serializedProtected)
|
||||
}
|
||||
raw.Header = obj.Signatures[0].header
|
||||
raw.Signature = newBuffer(obj.Signatures[0].Signature)
|
||||
} else {
|
||||
raw.Signatures = make([]rawSignatureInfo, len(obj.Signatures))
|
||||
for i, signature := range obj.Signatures {
|
||||
raw.Signatures[i] = rawSignatureInfo{
|
||||
Header: signature.header,
|
||||
Signature: newBuffer(signature.Signature),
|
||||
}
|
||||
|
||||
if signature.protected != nil {
|
||||
raw.Signatures[i].Protected = newBuffer(mustSerializeJSON(signature.protected))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string(mustSerializeJSON(raw))
|
||||
}
|
||||
334
vendor/gopkg.in/square/go-jose.v2/jwt/builder.go
generated
vendored
Normal file
334
vendor/gopkg.in/square/go-jose.v2/jwt/builder.go
generated
vendored
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
/*-
|
||||
* Copyright 2016 Zbigniew Mandziejewicz
|
||||
* Copyright 2016 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
|
||||
"gopkg.in/square/go-jose.v2/json"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
// Builder is a utility for making JSON Web Tokens. Calls can be chained, and
|
||||
// errors are accumulated until the final call to CompactSerialize/FullSerialize.
|
||||
type Builder interface {
|
||||
// Claims encodes claims into JWE/JWS form. Multiple calls will merge claims
|
||||
// into single JSON object. If you are passing private claims, make sure to set
|
||||
// struct field tags to specify the name for the JSON key to be used when
|
||||
// serializing.
|
||||
Claims(i interface{}) Builder
|
||||
// Token builds a JSONWebToken from provided data.
|
||||
Token() (*JSONWebToken, error)
|
||||
// FullSerialize serializes a token using the full serialization format.
|
||||
FullSerialize() (string, error)
|
||||
// CompactSerialize serializes a token using the compact serialization format.
|
||||
CompactSerialize() (string, error)
|
||||
}
|
||||
|
||||
// NestedBuilder is a utility for making Signed-Then-Encrypted JSON Web Tokens.
|
||||
// Calls can be chained, and errors are accumulated until final call to
|
||||
// CompactSerialize/FullSerialize.
|
||||
type NestedBuilder interface {
|
||||
// Claims encodes claims into JWE/JWS form. Multiple calls will merge claims
|
||||
// into single JSON object. If you are passing private claims, make sure to set
|
||||
// struct field tags to specify the name for the JSON key to be used when
|
||||
// serializing.
|
||||
Claims(i interface{}) NestedBuilder
|
||||
// Token builds a NestedJSONWebToken from provided data.
|
||||
Token() (*NestedJSONWebToken, error)
|
||||
// FullSerialize serializes a token using the full serialization format.
|
||||
FullSerialize() (string, error)
|
||||
// CompactSerialize serializes a token using the compact serialization format.
|
||||
CompactSerialize() (string, error)
|
||||
}
|
||||
|
||||
type builder struct {
|
||||
payload map[string]interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
type signedBuilder struct {
|
||||
builder
|
||||
sig jose.Signer
|
||||
}
|
||||
|
||||
type encryptedBuilder struct {
|
||||
builder
|
||||
enc jose.Encrypter
|
||||
}
|
||||
|
||||
type nestedBuilder struct {
|
||||
builder
|
||||
sig jose.Signer
|
||||
enc jose.Encrypter
|
||||
}
|
||||
|
||||
// Signed creates builder for signed tokens.
|
||||
func Signed(sig jose.Signer) Builder {
|
||||
return &signedBuilder{
|
||||
sig: sig,
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypted creates builder for encrypted tokens.
|
||||
func Encrypted(enc jose.Encrypter) Builder {
|
||||
return &encryptedBuilder{
|
||||
enc: enc,
|
||||
}
|
||||
}
|
||||
|
||||
// SignedAndEncrypted creates builder for signed-then-encrypted tokens.
|
||||
// ErrInvalidContentType will be returned if encrypter doesn't have JWT content type.
|
||||
func SignedAndEncrypted(sig jose.Signer, enc jose.Encrypter) NestedBuilder {
|
||||
if contentType, _ := enc.Options().ExtraHeaders[jose.HeaderContentType].(jose.ContentType); contentType != "JWT" {
|
||||
return &nestedBuilder{
|
||||
builder: builder{
|
||||
err: ErrInvalidContentType,
|
||||
},
|
||||
}
|
||||
}
|
||||
return &nestedBuilder{
|
||||
sig: sig,
|
||||
enc: enc,
|
||||
}
|
||||
}
|
||||
|
||||
func (b builder) claims(i interface{}) builder {
|
||||
if b.err != nil {
|
||||
return b
|
||||
}
|
||||
|
||||
m, ok := i.(map[string]interface{})
|
||||
switch {
|
||||
case ok:
|
||||
return b.merge(m)
|
||||
case reflect.Indirect(reflect.ValueOf(i)).Kind() == reflect.Struct:
|
||||
m, err := normalize(i)
|
||||
if err != nil {
|
||||
return builder{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
return b.merge(m)
|
||||
default:
|
||||
return builder{
|
||||
err: ErrInvalidClaims,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func normalize(i interface{}) (map[string]interface{}, error) {
|
||||
m := make(map[string]interface{})
|
||||
|
||||
raw, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := json.NewDecoder(bytes.NewReader(raw))
|
||||
d.UseNumber()
|
||||
|
||||
if err := d.Decode(&m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (b *builder) merge(m map[string]interface{}) builder {
|
||||
p := make(map[string]interface{})
|
||||
for k, v := range b.payload {
|
||||
p[k] = v
|
||||
}
|
||||
for k, v := range m {
|
||||
p[k] = v
|
||||
}
|
||||
|
||||
return builder{
|
||||
payload: p,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builder) token(p func(interface{}) ([]byte, error), h []jose.Header) (*JSONWebToken, error) {
|
||||
return &JSONWebToken{
|
||||
payload: p,
|
||||
Headers: h,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *signedBuilder) Claims(i interface{}) Builder {
|
||||
return &signedBuilder{
|
||||
builder: b.builder.claims(i),
|
||||
sig: b.sig,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *signedBuilder) Token() (*JSONWebToken, error) {
|
||||
sig, err := b.sign()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := make([]jose.Header, len(sig.Signatures))
|
||||
for i, v := range sig.Signatures {
|
||||
h[i] = v.Header
|
||||
}
|
||||
|
||||
return b.builder.token(sig.Verify, h)
|
||||
}
|
||||
|
||||
func (b *signedBuilder) CompactSerialize() (string, error) {
|
||||
sig, err := b.sign()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return sig.CompactSerialize()
|
||||
}
|
||||
|
||||
func (b *signedBuilder) FullSerialize() (string, error) {
|
||||
sig, err := b.sign()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return sig.FullSerialize(), nil
|
||||
}
|
||||
|
||||
func (b *signedBuilder) sign() (*jose.JSONWebSignature, error) {
|
||||
if b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
|
||||
p, err := json.Marshal(b.payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b.sig.Sign(p)
|
||||
}
|
||||
|
||||
func (b *encryptedBuilder) Claims(i interface{}) Builder {
|
||||
return &encryptedBuilder{
|
||||
builder: b.builder.claims(i),
|
||||
enc: b.enc,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *encryptedBuilder) CompactSerialize() (string, error) {
|
||||
enc, err := b.encrypt()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return enc.CompactSerialize()
|
||||
}
|
||||
|
||||
func (b *encryptedBuilder) FullSerialize() (string, error) {
|
||||
enc, err := b.encrypt()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return enc.FullSerialize(), nil
|
||||
}
|
||||
|
||||
func (b *encryptedBuilder) Token() (*JSONWebToken, error) {
|
||||
enc, err := b.encrypt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b.builder.token(enc.Decrypt, []jose.Header{enc.Header})
|
||||
}
|
||||
|
||||
func (b *encryptedBuilder) encrypt() (*jose.JSONWebEncryption, error) {
|
||||
if b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
|
||||
p, err := json.Marshal(b.payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b.enc.Encrypt(p)
|
||||
}
|
||||
|
||||
func (b *nestedBuilder) Claims(i interface{}) NestedBuilder {
|
||||
return &nestedBuilder{
|
||||
builder: b.builder.claims(i),
|
||||
sig: b.sig,
|
||||
enc: b.enc,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *nestedBuilder) Token() (*NestedJSONWebToken, error) {
|
||||
enc, err := b.signAndEncrypt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &NestedJSONWebToken{
|
||||
enc: enc,
|
||||
Headers: []jose.Header{enc.Header},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *nestedBuilder) CompactSerialize() (string, error) {
|
||||
enc, err := b.signAndEncrypt()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return enc.CompactSerialize()
|
||||
}
|
||||
|
||||
func (b *nestedBuilder) FullSerialize() (string, error) {
|
||||
enc, err := b.signAndEncrypt()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return enc.FullSerialize(), nil
|
||||
}
|
||||
|
||||
func (b *nestedBuilder) signAndEncrypt() (*jose.JSONWebEncryption, error) {
|
||||
if b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
|
||||
p, err := json.Marshal(b.payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sig, err := b.sig.Sign(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p2, err := sig.CompactSerialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b.enc.Encrypt([]byte(p2))
|
||||
}
|
||||
115
vendor/gopkg.in/square/go-jose.v2/jwt/claims.go
generated
vendored
Normal file
115
vendor/gopkg.in/square/go-jose.v2/jwt/claims.go
generated
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/*-
|
||||
* Copyright 2016 Zbigniew Mandziejewicz
|
||||
* Copyright 2016 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Claims represents public claim values (as specified in RFC 7519).
|
||||
type Claims struct {
|
||||
Issuer string `json:"iss,omitempty"`
|
||||
Subject string `json:"sub,omitempty"`
|
||||
Audience Audience `json:"aud,omitempty"`
|
||||
Expiry NumericDate `json:"exp,omitempty"`
|
||||
NotBefore NumericDate `json:"nbf,omitempty"`
|
||||
IssuedAt NumericDate `json:"iat,omitempty"`
|
||||
ID string `json:"jti,omitempty"`
|
||||
}
|
||||
|
||||
// NumericDate represents date and time as the number of seconds since the
|
||||
// epoch, including leap seconds. Non-integer values can be represented
|
||||
// in the serialized format, but we round to the nearest second.
|
||||
type NumericDate int64
|
||||
|
||||
// NewNumericDate constructs NumericDate from time.Time value.
|
||||
func NewNumericDate(t time.Time) NumericDate {
|
||||
if t.IsZero() {
|
||||
return NumericDate(0)
|
||||
}
|
||||
|
||||
// While RFC 7519 technically states that NumericDate values may be
|
||||
// non-integer values, we don't bother serializing timestamps in
|
||||
// claims with sub-second accurancy and just round to the nearest
|
||||
// second instead. Not convined sub-second accuracy is useful here.
|
||||
return NumericDate(t.Unix())
|
||||
}
|
||||
|
||||
// MarshalJSON serializes the given NumericDate into its JSON representation.
|
||||
func (n NumericDate) MarshalJSON() ([]byte, error) {
|
||||
return []byte(strconv.FormatInt(int64(n), 10)), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON reads a date from its JSON representation.
|
||||
func (n *NumericDate) UnmarshalJSON(b []byte) error {
|
||||
s := string(b)
|
||||
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return ErrUnmarshalNumericDate
|
||||
}
|
||||
|
||||
*n = NumericDate(f)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Time returns time.Time representation of NumericDate.
|
||||
func (n NumericDate) Time() time.Time {
|
||||
return time.Unix(int64(n), 0)
|
||||
}
|
||||
|
||||
// Audience represents the recipents that the token is intended for.
|
||||
type Audience []string
|
||||
|
||||
// UnmarshalJSON reads an audience from its JSON representation.
|
||||
func (s *Audience) UnmarshalJSON(b []byte) error {
|
||||
var v interface{}
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
*s = []string{v}
|
||||
case []interface{}:
|
||||
a := make([]string, len(v))
|
||||
for i, e := range v {
|
||||
s, ok := e.(string)
|
||||
if !ok {
|
||||
return ErrUnmarshalAudience
|
||||
}
|
||||
a[i] = s
|
||||
}
|
||||
*s = a
|
||||
default:
|
||||
return ErrUnmarshalAudience
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Audience) Contains(v string) bool {
|
||||
for _, a := range s {
|
||||
if a == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
22
vendor/gopkg.in/square/go-jose.v2/jwt/doc.go
generated
vendored
Normal file
22
vendor/gopkg.in/square/go-jose.v2/jwt/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/*-
|
||||
* Copyright 2017 Square Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
Package jwt provides an implementation of the JSON Web Token standard.
|
||||
|
||||
*/
|
||||
package jwt
|
||||
50
vendor/gopkg.in/square/go-jose.v2/jwt/errors.go
generated
vendored
Normal file
50
vendor/gopkg.in/square/go-jose.v2/jwt/errors.go
generated
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/*-
|
||||
* Copyright 2016 Zbigniew Mandziejewicz
|
||||
* Copyright 2016 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jwt
|
||||
|
||||
import "errors"
|
||||
|
||||
// ErrUnmarshalAudience indicates that aud claim could not be unmarshalled.
|
||||
var ErrUnmarshalAudience = errors.New("square/go-jose/jwt: expected string or array value to unmarshal to Audience")
|
||||
|
||||
// ErrUnmarshalNumericDate indicates that JWT NumericDate could not be unmarshalled.
|
||||
var ErrUnmarshalNumericDate = errors.New("square/go-jose/jwt: expected number value to unmarshal NumericDate")
|
||||
|
||||
// ErrInvalidClaims indicates that given claims have invalid type.
|
||||
var ErrInvalidClaims = errors.New("square/go-jose/jwt: expected claims to be value convertible into JSON object")
|
||||
|
||||
// ErrInvalidIssuer indicates invalid iss claim.
|
||||
var ErrInvalidIssuer = errors.New("square/go-jose/jwt: validation failed, invalid issuer claim (iss)")
|
||||
|
||||
// ErrInvalidSubject indicates invalid sub claim.
|
||||
var ErrInvalidSubject = errors.New("square/go-jose/jwt: validation failed, invalid subject claim (sub)")
|
||||
|
||||
// ErrInvalidAudience indicated invalid aud claim.
|
||||
var ErrInvalidAudience = errors.New("square/go-jose/jwt: validation failed, invalid audience claim (aud)")
|
||||
|
||||
// ErrInvalidID indicates invalid jti claim.
|
||||
var ErrInvalidID = errors.New("square/go-jose/jwt: validation failed, invalid ID claim (jti)")
|
||||
|
||||
// ErrNotValidYet indicates that token is used before time indicated in nbf claim.
|
||||
var ErrNotValidYet = errors.New("square/go-jose/jwt: validation failed, token not valid yet (nbf)")
|
||||
|
||||
// ErrExpired indicates that token is used after expiry time indicated in exp claim.
|
||||
var ErrExpired = errors.New("square/go-jose/jwt: validation failed, token is expired (exp)")
|
||||
|
||||
// ErrInvalidContentType indicated that token requires JWT cty header.
|
||||
var ErrInvalidContentType = errors.New("square/go-jose/jwt: expected content type to be JWT (cty header)")
|
||||
113
vendor/gopkg.in/square/go-jose.v2/jwt/jwt.go
generated
vendored
Normal file
113
vendor/gopkg.in/square/go-jose.v2/jwt/jwt.go
generated
vendored
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/*-
|
||||
* Copyright 2016 Zbigniew Mandziejewicz
|
||||
* Copyright 2016 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
"gopkg.in/square/go-jose.v2/json"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// JSONWebToken represents a JSON Web Token (as specified in RFC7519).
|
||||
type JSONWebToken struct {
|
||||
payload func(k interface{}) ([]byte, error)
|
||||
Headers []jose.Header
|
||||
}
|
||||
|
||||
type NestedJSONWebToken struct {
|
||||
enc *jose.JSONWebEncryption
|
||||
Headers []jose.Header
|
||||
}
|
||||
|
||||
// Claims deserializes a JSONWebToken into dest using the provided key.
|
||||
func (t *JSONWebToken) Claims(key interface{}, dest ...interface{}) error {
|
||||
b, err := t.payload(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, d := range dest {
|
||||
if err := json.Unmarshal(b, d); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *NestedJSONWebToken) Decrypt(decryptionKey interface{}) (*JSONWebToken, error) {
|
||||
b, err := t.enc.Decrypt(decryptionKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sig, err := ParseSigned(string(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
// ParseSigned parses token from JWS form.
|
||||
func ParseSigned(s string) (*JSONWebToken, error) {
|
||||
sig, err := jose.ParseSigned(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headers := make([]jose.Header, len(sig.Signatures))
|
||||
for i, signature := range sig.Signatures {
|
||||
headers[i] = signature.Header
|
||||
}
|
||||
|
||||
return &JSONWebToken{
|
||||
payload: sig.Verify,
|
||||
Headers: headers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseEncrypted parses token from JWE form.
|
||||
func ParseEncrypted(s string) (*JSONWebToken, error) {
|
||||
enc, err := jose.ParseEncrypted(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &JSONWebToken{
|
||||
payload: enc.Decrypt,
|
||||
Headers: []jose.Header{enc.Header},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseSignedAndEncrypted parses signed-then-encrypted token from JWE form.
|
||||
func ParseSignedAndEncrypted(s string) (*NestedJSONWebToken, error) {
|
||||
enc, err := jose.ParseEncrypted(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contentType, _ := enc.Header.ExtraHeaders[jose.HeaderContentType].(string)
|
||||
if strings.ToUpper(contentType) != "JWT" {
|
||||
return nil, ErrInvalidContentType
|
||||
}
|
||||
|
||||
return &NestedJSONWebToken{
|
||||
enc: enc,
|
||||
Headers: []jose.Header{enc.Header},
|
||||
}, nil
|
||||
}
|
||||
89
vendor/gopkg.in/square/go-jose.v2/jwt/validation.go
generated
vendored
Normal file
89
vendor/gopkg.in/square/go-jose.v2/jwt/validation.go
generated
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/*-
|
||||
* Copyright 2016 Zbigniew Mandziejewicz
|
||||
* Copyright 2016 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jwt
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
// DefaultLeeway defines the default leeway for matching NotBefore/Expiry claims.
|
||||
DefaultLeeway = 1.0 * time.Minute
|
||||
)
|
||||
|
||||
// Expected defines values used for protected claims validation.
|
||||
// If field has zero value then validation is skipped.
|
||||
type Expected struct {
|
||||
// Issuer matches the "iss" claim exactly.
|
||||
Issuer string
|
||||
// Subject matches the "sub" claim exactly.
|
||||
Subject string
|
||||
// Audience matches the values in "aud" claim, regardless of their order.
|
||||
Audience Audience
|
||||
// ID matches the "jti" claim exactly.
|
||||
ID string
|
||||
// Time matches the "exp" and "ebf" claims with leeway.
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
// WithTime copies expectations with new time.
|
||||
func (e Expected) WithTime(t time.Time) Expected {
|
||||
e.Time = t
|
||||
return e
|
||||
}
|
||||
|
||||
// Validate checks claims in a token against expected values.
|
||||
// A default leeway value of one minute is used to compare time values.
|
||||
func (c Claims) Validate(e Expected) error {
|
||||
return c.ValidateWithLeeway(e, DefaultLeeway)
|
||||
}
|
||||
|
||||
// ValidateWithLeeway checks claims in a token against expected values. A
|
||||
// custom leeway may be specified for comparing time values. You may pass a
|
||||
// zero value to check time values with no leeway, but you should not that
|
||||
// numeric date values are rounded to the nearest second and sub-second
|
||||
// precision is not supported.
|
||||
func (c Claims) ValidateWithLeeway(e Expected, leeway time.Duration) error {
|
||||
if e.Issuer != "" && e.Issuer != c.Issuer {
|
||||
return ErrInvalidIssuer
|
||||
}
|
||||
|
||||
if e.Subject != "" && e.Subject != c.Subject {
|
||||
return ErrInvalidSubject
|
||||
}
|
||||
|
||||
if e.ID != "" && e.ID != c.ID {
|
||||
return ErrInvalidID
|
||||
}
|
||||
|
||||
if len(e.Audience) != 0 {
|
||||
for _, v := range e.Audience {
|
||||
if !c.Audience.Contains(v) {
|
||||
return ErrInvalidAudience
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !e.Time.IsZero() && e.Time.Add(leeway).Before(c.NotBefore.Time()) {
|
||||
return ErrNotValidYet
|
||||
}
|
||||
|
||||
if !e.Time.IsZero() && e.Time.Add(-leeway).After(c.Expiry.Time()) {
|
||||
return ErrExpired
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
417
vendor/gopkg.in/square/go-jose.v2/shared.go
generated
vendored
Normal file
417
vendor/gopkg.in/square/go-jose.v2/shared.go
generated
vendored
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
/*-
|
||||
* Copyright 2014 Square Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jose
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/square/go-jose.v2/json"
|
||||
)
|
||||
|
||||
// KeyAlgorithm represents a key management algorithm.
|
||||
type KeyAlgorithm string
|
||||
|
||||
// SignatureAlgorithm represents a signature (or MAC) algorithm.
|
||||
type SignatureAlgorithm string
|
||||
|
||||
// ContentEncryption represents a content encryption algorithm.
|
||||
type ContentEncryption string
|
||||
|
||||
// CompressionAlgorithm represents an algorithm used for plaintext compression.
|
||||
type CompressionAlgorithm string
|
||||
|
||||
// ContentType represents type of the contained data.
|
||||
type ContentType string
|
||||
|
||||
var (
|
||||
// ErrCryptoFailure represents an error in cryptographic primitive. This
|
||||
// occurs when, for example, a message had an invalid authentication tag or
|
||||
// could not be decrypted.
|
||||
ErrCryptoFailure = errors.New("square/go-jose: error in cryptographic primitive")
|
||||
|
||||
// ErrUnsupportedAlgorithm indicates that a selected algorithm is not
|
||||
// supported. This occurs when trying to instantiate an encrypter for an
|
||||
// algorithm that is not yet implemented.
|
||||
ErrUnsupportedAlgorithm = errors.New("square/go-jose: unknown/unsupported algorithm")
|
||||
|
||||
// ErrUnsupportedKeyType indicates that the given key type/format is not
|
||||
// supported. This occurs when trying to instantiate an encrypter and passing
|
||||
// it a key of an unrecognized type or with unsupported parameters, such as
|
||||
// an RSA private key with more than two primes.
|
||||
ErrUnsupportedKeyType = errors.New("square/go-jose: unsupported key type/format")
|
||||
|
||||
// ErrNotSupported serialization of object is not supported. This occurs when
|
||||
// trying to compact-serialize an object which can't be represented in
|
||||
// compact form.
|
||||
ErrNotSupported = errors.New("square/go-jose: compact serialization not supported for object")
|
||||
|
||||
// ErrUnprotectedNonce indicates that while parsing a JWS or JWE object, a
|
||||
// nonce header parameter was included in an unprotected header object.
|
||||
ErrUnprotectedNonce = errors.New("square/go-jose: Nonce parameter included in unprotected header")
|
||||
)
|
||||
|
||||
// Key management algorithms
|
||||
const (
|
||||
ED25519 = KeyAlgorithm("ED25519")
|
||||
RSA1_5 = KeyAlgorithm("RSA1_5") // RSA-PKCS1v1.5
|
||||
RSA_OAEP = KeyAlgorithm("RSA-OAEP") // RSA-OAEP-SHA1
|
||||
RSA_OAEP_256 = KeyAlgorithm("RSA-OAEP-256") // RSA-OAEP-SHA256
|
||||
A128KW = KeyAlgorithm("A128KW") // AES key wrap (128)
|
||||
A192KW = KeyAlgorithm("A192KW") // AES key wrap (192)
|
||||
A256KW = KeyAlgorithm("A256KW") // AES key wrap (256)
|
||||
DIRECT = KeyAlgorithm("dir") // Direct encryption
|
||||
ECDH_ES = KeyAlgorithm("ECDH-ES") // ECDH-ES
|
||||
ECDH_ES_A128KW = KeyAlgorithm("ECDH-ES+A128KW") // ECDH-ES + AES key wrap (128)
|
||||
ECDH_ES_A192KW = KeyAlgorithm("ECDH-ES+A192KW") // ECDH-ES + AES key wrap (192)
|
||||
ECDH_ES_A256KW = KeyAlgorithm("ECDH-ES+A256KW") // ECDH-ES + AES key wrap (256)
|
||||
A128GCMKW = KeyAlgorithm("A128GCMKW") // AES-GCM key wrap (128)
|
||||
A192GCMKW = KeyAlgorithm("A192GCMKW") // AES-GCM key wrap (192)
|
||||
A256GCMKW = KeyAlgorithm("A256GCMKW") // AES-GCM key wrap (256)
|
||||
PBES2_HS256_A128KW = KeyAlgorithm("PBES2-HS256+A128KW") // PBES2 + HMAC-SHA256 + AES key wrap (128)
|
||||
PBES2_HS384_A192KW = KeyAlgorithm("PBES2-HS384+A192KW") // PBES2 + HMAC-SHA384 + AES key wrap (192)
|
||||
PBES2_HS512_A256KW = KeyAlgorithm("PBES2-HS512+A256KW") // PBES2 + HMAC-SHA512 + AES key wrap (256)
|
||||
)
|
||||
|
||||
// Signature algorithms
|
||||
const (
|
||||
EdDSA = SignatureAlgorithm("EdDSA")
|
||||
HS256 = SignatureAlgorithm("HS256") // HMAC using SHA-256
|
||||
HS384 = SignatureAlgorithm("HS384") // HMAC using SHA-384
|
||||
HS512 = SignatureAlgorithm("HS512") // HMAC using SHA-512
|
||||
RS256 = SignatureAlgorithm("RS256") // RSASSA-PKCS-v1.5 using SHA-256
|
||||
RS384 = SignatureAlgorithm("RS384") // RSASSA-PKCS-v1.5 using SHA-384
|
||||
RS512 = SignatureAlgorithm("RS512") // RSASSA-PKCS-v1.5 using SHA-512
|
||||
ES256 = SignatureAlgorithm("ES256") // ECDSA using P-256 and SHA-256
|
||||
ES384 = SignatureAlgorithm("ES384") // ECDSA using P-384 and SHA-384
|
||||
ES512 = SignatureAlgorithm("ES512") // ECDSA using P-521 and SHA-512
|
||||
PS256 = SignatureAlgorithm("PS256") // RSASSA-PSS using SHA256 and MGF1-SHA256
|
||||
PS384 = SignatureAlgorithm("PS384") // RSASSA-PSS using SHA384 and MGF1-SHA384
|
||||
PS512 = SignatureAlgorithm("PS512") // RSASSA-PSS using SHA512 and MGF1-SHA512
|
||||
)
|
||||
|
||||
// Content encryption algorithms
|
||||
const (
|
||||
A128CBC_HS256 = ContentEncryption("A128CBC-HS256") // AES-CBC + HMAC-SHA256 (128)
|
||||
A192CBC_HS384 = ContentEncryption("A192CBC-HS384") // AES-CBC + HMAC-SHA384 (192)
|
||||
A256CBC_HS512 = ContentEncryption("A256CBC-HS512") // AES-CBC + HMAC-SHA512 (256)
|
||||
A128GCM = ContentEncryption("A128GCM") // AES-GCM (128)
|
||||
A192GCM = ContentEncryption("A192GCM") // AES-GCM (192)
|
||||
A256GCM = ContentEncryption("A256GCM") // AES-GCM (256)
|
||||
)
|
||||
|
||||
// Compression algorithms
|
||||
const (
|
||||
NONE = CompressionAlgorithm("") // No compression
|
||||
DEFLATE = CompressionAlgorithm("DEF") // DEFLATE (RFC 1951)
|
||||
)
|
||||
|
||||
// A key in the protected header of a JWS object. Use of the Header...
|
||||
// constants is preferred to enhance type safety.
|
||||
type HeaderKey string
|
||||
|
||||
const (
|
||||
HeaderType HeaderKey = "typ" // string
|
||||
HeaderContentType = "cty" // string
|
||||
|
||||
// These are set by go-jose and shouldn't need to be set by consumers of the
|
||||
// library.
|
||||
headerAlgorithm = "alg" // string
|
||||
headerEncryption = "enc" // ContentEncryption
|
||||
headerCompression = "zip" // CompressionAlgorithm
|
||||
headerCritical = "crit" // []string
|
||||
|
||||
headerAPU = "apu" // *byteBuffer
|
||||
headerAPV = "apv" // *byteBuffer
|
||||
headerEPK = "epk" // *JSONWebKey
|
||||
headerIV = "iv" // *byteBuffer
|
||||
headerTag = "tag" // *byteBuffer
|
||||
|
||||
headerJWK = "jwk" // *JSONWebKey
|
||||
headerKeyID = "kid" // string
|
||||
headerNonce = "nonce" // string
|
||||
)
|
||||
|
||||
// rawHeader represents the JOSE header for JWE/JWS objects (used for parsing).
|
||||
//
|
||||
// The decoding of the constituent items is deferred because we want to marshal
|
||||
// some members into particular structs rather than generic maps, but at the
|
||||
// same time we need to receive any extra fields unhandled by this library to
|
||||
// pass through to consuming code in case it wants to examine them.
|
||||
type rawHeader map[HeaderKey]*json.RawMessage
|
||||
|
||||
// Header represents the read-only JOSE header for JWE/JWS objects.
|
||||
type Header struct {
|
||||
KeyID string
|
||||
JSONWebKey *JSONWebKey
|
||||
Algorithm string
|
||||
Nonce string
|
||||
|
||||
// Any headers not recognised above get unmarshaled from JSON in a generic
|
||||
// manner and placed in this map.
|
||||
ExtraHeaders map[HeaderKey]interface{}
|
||||
}
|
||||
|
||||
func (parsed rawHeader) set(k HeaderKey, v interface{}) error {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parsed[k] = makeRawMessage(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getString gets a string from the raw JSON, defaulting to "".
|
||||
func (parsed rawHeader) getString(k HeaderKey) string {
|
||||
v, ok := parsed[k]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
var s string
|
||||
err := json.Unmarshal(*v, &s)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// getByteBuffer gets a byte buffer from the raw JSON. Returns (nil, nil) if
|
||||
// not specified.
|
||||
func (parsed rawHeader) getByteBuffer(k HeaderKey) (*byteBuffer, error) {
|
||||
v := parsed[k]
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var bb *byteBuffer
|
||||
err := json.Unmarshal(*v, &bb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bb, nil
|
||||
}
|
||||
|
||||
// getAlgorithm extracts parsed "alg" from the raw JSON as a KeyAlgorithm.
|
||||
func (parsed rawHeader) getAlgorithm() KeyAlgorithm {
|
||||
return KeyAlgorithm(parsed.getString(headerAlgorithm))
|
||||
}
|
||||
|
||||
// getSignatureAlgorithm extracts parsed "alg" from the raw JSON as a SignatureAlgorithm.
|
||||
func (parsed rawHeader) getSignatureAlgorithm() SignatureAlgorithm {
|
||||
return SignatureAlgorithm(parsed.getString(headerAlgorithm))
|
||||
}
|
||||
|
||||
// getEncryption extracts parsed "enc" from the raw JSON.
|
||||
func (parsed rawHeader) getEncryption() ContentEncryption {
|
||||
return ContentEncryption(parsed.getString(headerEncryption))
|
||||
}
|
||||
|
||||
// getCompression extracts parsed "zip" from the raw JSON.
|
||||
func (parsed rawHeader) getCompression() CompressionAlgorithm {
|
||||
return CompressionAlgorithm(parsed.getString(headerCompression))
|
||||
}
|
||||
|
||||
func (parsed rawHeader) getNonce() string {
|
||||
return parsed.getString(headerNonce)
|
||||
}
|
||||
|
||||
// getEPK extracts parsed "epk" from the raw JSON.
|
||||
func (parsed rawHeader) getEPK() (*JSONWebKey, error) {
|
||||
v := parsed[headerEPK]
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var epk *JSONWebKey
|
||||
err := json.Unmarshal(*v, &epk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return epk, nil
|
||||
}
|
||||
|
||||
// getAPU extracts parsed "apu" from the raw JSON.
|
||||
func (parsed rawHeader) getAPU() (*byteBuffer, error) {
|
||||
return parsed.getByteBuffer(headerAPU)
|
||||
}
|
||||
|
||||
// getAPV extracts parsed "apv" from the raw JSON.
|
||||
func (parsed rawHeader) getAPV() (*byteBuffer, error) {
|
||||
return parsed.getByteBuffer(headerAPV)
|
||||
}
|
||||
|
||||
// getIV extracts parsed "iv" frpom the raw JSON.
|
||||
func (parsed rawHeader) getIV() (*byteBuffer, error) {
|
||||
return parsed.getByteBuffer(headerIV)
|
||||
}
|
||||
|
||||
// getTag extracts parsed "tag" frpom the raw JSON.
|
||||
func (parsed rawHeader) getTag() (*byteBuffer, error) {
|
||||
return parsed.getByteBuffer(headerTag)
|
||||
}
|
||||
|
||||
// getJWK extracts parsed "jwk" from the raw JSON.
|
||||
func (parsed rawHeader) getJWK() (*JSONWebKey, error) {
|
||||
v := parsed[headerJWK]
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var jwk *JSONWebKey
|
||||
err := json.Unmarshal(*v, &jwk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return jwk, nil
|
||||
}
|
||||
|
||||
// getCritical extracts parsed "crit" from the raw JSON. If omitted, it
|
||||
// returns an empty slice.
|
||||
func (parsed rawHeader) getCritical() ([]string, error) {
|
||||
v := parsed[headerCritical]
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var q []string
|
||||
err := json.Unmarshal(*v, &q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
// sanitized produces a cleaned-up header object from the raw JSON.
|
||||
func (parsed rawHeader) sanitized() (h Header, err error) {
|
||||
for k, v := range parsed {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
switch k {
|
||||
case headerJWK:
|
||||
var jwk *JSONWebKey
|
||||
err = json.Unmarshal(*v, &jwk)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to unmarshal JWK: %v: %#v", err, string(*v))
|
||||
return
|
||||
}
|
||||
h.JSONWebKey = jwk
|
||||
case headerKeyID:
|
||||
var s string
|
||||
err = json.Unmarshal(*v, &s)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to unmarshal key ID: %v: %#v", err, string(*v))
|
||||
return
|
||||
}
|
||||
h.KeyID = s
|
||||
case headerAlgorithm:
|
||||
var s string
|
||||
err = json.Unmarshal(*v, &s)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to unmarshal algorithm: %v: %#v", err, string(*v))
|
||||
return
|
||||
}
|
||||
h.Algorithm = s
|
||||
case headerNonce:
|
||||
var s string
|
||||
err = json.Unmarshal(*v, &s)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to unmarshal nonce: %v: %#v", err, string(*v))
|
||||
return
|
||||
}
|
||||
h.Nonce = s
|
||||
default:
|
||||
if h.ExtraHeaders == nil {
|
||||
h.ExtraHeaders = map[HeaderKey]interface{}{}
|
||||
}
|
||||
var v2 interface{}
|
||||
err = json.Unmarshal(*v, &v2)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to unmarshal value: %v: %#v", err, string(*v))
|
||||
return
|
||||
}
|
||||
h.ExtraHeaders[k] = v2
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dst rawHeader) isSet(k HeaderKey) bool {
|
||||
dvr := dst[k]
|
||||
if dvr == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var dv interface{}
|
||||
err := json.Unmarshal(*dvr, &dv)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if dvStr, ok := dv.(string); ok {
|
||||
return dvStr != ""
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Merge headers from src into dst, giving precedence to headers from l.
|
||||
func (dst rawHeader) merge(src *rawHeader) {
|
||||
if src == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range *src {
|
||||
if dst.isSet(k) {
|
||||
continue
|
||||
}
|
||||
|
||||
dst[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Get JOSE name of curve
|
||||
func curveName(crv elliptic.Curve) (string, error) {
|
||||
switch crv {
|
||||
case elliptic.P256():
|
||||
return "P-256", nil
|
||||
case elliptic.P384():
|
||||
return "P-384", nil
|
||||
case elliptic.P521():
|
||||
return "P-521", nil
|
||||
default:
|
||||
return "", fmt.Errorf("square/go-jose: unsupported/unknown elliptic curve")
|
||||
}
|
||||
}
|
||||
|
||||
// Get size of curve in bytes
|
||||
func curveSize(crv elliptic.Curve) int {
|
||||
bits := crv.Params().BitSize
|
||||
|
||||
div := bits / 8
|
||||
mod := bits % 8
|
||||
|
||||
if mod == 0 {
|
||||
return div
|
||||
}
|
||||
|
||||
return div + 1
|
||||
}
|
||||
|
||||
func makeRawMessage(b []byte) *json.RawMessage {
|
||||
rm := json.RawMessage(b)
|
||||
return &rm
|
||||
}
|
||||
343
vendor/gopkg.in/square/go-jose.v2/signing.go
generated
vendored
Normal file
343
vendor/gopkg.in/square/go-jose.v2/signing.go
generated
vendored
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
/*-
|
||||
* Copyright 2014 Square Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jose
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
|
||||
"gopkg.in/square/go-jose.v2/json"
|
||||
)
|
||||
|
||||
// NonceSource represents a source of random nonces to go into JWS objects
|
||||
type NonceSource interface {
|
||||
Nonce() (string, error)
|
||||
}
|
||||
|
||||
// Signer represents a signer which takes a payload and produces a signed JWS object.
|
||||
type Signer interface {
|
||||
Sign(payload []byte) (*JSONWebSignature, error)
|
||||
Options() SignerOptions
|
||||
}
|
||||
|
||||
// SigningKey represents an algorithm/key used to sign a message.
|
||||
type SigningKey struct {
|
||||
Algorithm SignatureAlgorithm
|
||||
Key interface{}
|
||||
}
|
||||
|
||||
// SignerOptions represents options that can be set when creating signers.
|
||||
type SignerOptions struct {
|
||||
NonceSource NonceSource
|
||||
EmbedJWK bool
|
||||
|
||||
// Optional map of additional keys to be inserted into the protected header
|
||||
// of a JWS object. Some specifications which make use of JWS like to insert
|
||||
// additional values here. All values must be JSON-serializable.
|
||||
ExtraHeaders map[HeaderKey]interface{}
|
||||
}
|
||||
|
||||
// WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it
|
||||
// if necessary. It returns itself and so can be used in a fluent style.
|
||||
func (so *SignerOptions) WithHeader(k HeaderKey, v interface{}) *SignerOptions {
|
||||
if so.ExtraHeaders == nil {
|
||||
so.ExtraHeaders = map[HeaderKey]interface{}{}
|
||||
}
|
||||
so.ExtraHeaders[k] = v
|
||||
return so
|
||||
}
|
||||
|
||||
// WithContentType adds a content type ("cty") header and returns the updated
|
||||
// SignerOptions.
|
||||
func (so *SignerOptions) WithContentType(contentType ContentType) *SignerOptions {
|
||||
return so.WithHeader(HeaderContentType, contentType)
|
||||
}
|
||||
|
||||
// WithType adds a type ("typ") header and returns the updated SignerOptions.
|
||||
func (so *SignerOptions) WithType(typ ContentType) *SignerOptions {
|
||||
return so.WithHeader(HeaderType, typ)
|
||||
}
|
||||
|
||||
type payloadSigner interface {
|
||||
signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error)
|
||||
}
|
||||
|
||||
type payloadVerifier interface {
|
||||
verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error
|
||||
}
|
||||
|
||||
type genericSigner struct {
|
||||
recipients []recipientSigInfo
|
||||
nonceSource NonceSource
|
||||
embedJWK bool
|
||||
extraHeaders map[HeaderKey]interface{}
|
||||
}
|
||||
|
||||
type recipientSigInfo struct {
|
||||
sigAlg SignatureAlgorithm
|
||||
publicKey *JSONWebKey
|
||||
signer payloadSigner
|
||||
}
|
||||
|
||||
// NewSigner creates an appropriate signer based on the key type
|
||||
func NewSigner(sig SigningKey, opts *SignerOptions) (Signer, error) {
|
||||
return NewMultiSigner([]SigningKey{sig}, opts)
|
||||
}
|
||||
|
||||
// NewMultiSigner creates a signer for multiple recipients
|
||||
func NewMultiSigner(sigs []SigningKey, opts *SignerOptions) (Signer, error) {
|
||||
signer := &genericSigner{recipients: []recipientSigInfo{}}
|
||||
|
||||
if opts != nil {
|
||||
signer.nonceSource = opts.NonceSource
|
||||
signer.embedJWK = opts.EmbedJWK
|
||||
signer.extraHeaders = opts.ExtraHeaders
|
||||
}
|
||||
|
||||
for _, sig := range sigs {
|
||||
err := signer.addRecipient(sig.Algorithm, sig.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
// newVerifier creates a verifier based on the key type
|
||||
func newVerifier(verificationKey interface{}) (payloadVerifier, error) {
|
||||
switch verificationKey := verificationKey.(type) {
|
||||
case ed25519.PublicKey:
|
||||
return &edEncrypterVerifier{
|
||||
publicKey: verificationKey,
|
||||
}, nil
|
||||
case *rsa.PublicKey:
|
||||
return &rsaEncrypterVerifier{
|
||||
publicKey: verificationKey,
|
||||
}, nil
|
||||
case *ecdsa.PublicKey:
|
||||
return &ecEncrypterVerifier{
|
||||
publicKey: verificationKey,
|
||||
}, nil
|
||||
case []byte:
|
||||
return &symmetricMac{
|
||||
key: verificationKey,
|
||||
}, nil
|
||||
case JSONWebKey:
|
||||
return newVerifier(verificationKey.Key)
|
||||
case *JSONWebKey:
|
||||
return newVerifier(verificationKey.Key)
|
||||
default:
|
||||
return nil, ErrUnsupportedKeyType
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *genericSigner) addRecipient(alg SignatureAlgorithm, signingKey interface{}) error {
|
||||
recipient, err := makeJWSRecipient(alg, signingKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.recipients = append(ctx.recipients, recipient)
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeJWSRecipient(alg SignatureAlgorithm, signingKey interface{}) (recipientSigInfo, error) {
|
||||
switch signingKey := signingKey.(type) {
|
||||
case ed25519.PrivateKey:
|
||||
return newEd25519Signer(alg, signingKey)
|
||||
case *rsa.PrivateKey:
|
||||
return newRSASigner(alg, signingKey)
|
||||
case *ecdsa.PrivateKey:
|
||||
return newECDSASigner(alg, signingKey)
|
||||
case []byte:
|
||||
return newSymmetricSigner(alg, signingKey)
|
||||
case JSONWebKey:
|
||||
return newJWKSigner(alg, signingKey)
|
||||
case *JSONWebKey:
|
||||
return newJWKSigner(alg, *signingKey)
|
||||
default:
|
||||
return recipientSigInfo{}, ErrUnsupportedKeyType
|
||||
}
|
||||
}
|
||||
|
||||
func newJWKSigner(alg SignatureAlgorithm, signingKey JSONWebKey) (recipientSigInfo, error) {
|
||||
recipient, err := makeJWSRecipient(alg, signingKey.Key)
|
||||
if err != nil {
|
||||
return recipientSigInfo{}, err
|
||||
}
|
||||
if recipient.publicKey != nil {
|
||||
// recipient.publicKey is a JWK synthesized for embedding when recipientSigInfo
|
||||
// was created for the inner key (such as a RSA or ECDSA public key). It contains
|
||||
// the pub key for embedding, but doesn't have extra params like key id.
|
||||
publicKey := signingKey
|
||||
publicKey.Key = recipient.publicKey.Key
|
||||
recipient.publicKey = &publicKey
|
||||
|
||||
// This should be impossible, but let's check anyway.
|
||||
if !recipient.publicKey.IsPublic() {
|
||||
return recipientSigInfo{}, errors.New("square/go-jose: public key was unexpectedly not public")
|
||||
}
|
||||
}
|
||||
return recipient, nil
|
||||
}
|
||||
|
||||
func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
|
||||
obj := &JSONWebSignature{}
|
||||
obj.payload = payload
|
||||
obj.Signatures = make([]Signature, len(ctx.recipients))
|
||||
|
||||
for i, recipient := range ctx.recipients {
|
||||
protected := map[HeaderKey]interface{}{
|
||||
headerAlgorithm: string(recipient.sigAlg),
|
||||
}
|
||||
|
||||
if recipient.publicKey != nil {
|
||||
// We want to embed the JWK or set the kid header, but not both. Having a protected
|
||||
// header that contains an embedded JWK while also simultaneously containing the kid
|
||||
// header is confusing, and at least in ACME the two are considered to be mutually
|
||||
// exclusive. The fact that both can exist at the same time is a somewhat unfortunate
|
||||
// result of the JOSE spec. We've decided that this library will only include one or
|
||||
// the other to avoid this confusion.
|
||||
//
|
||||
// See https://github.com/square/go-jose/issues/157 for more context.
|
||||
if ctx.embedJWK {
|
||||
protected[headerJWK] = recipient.publicKey
|
||||
} else {
|
||||
protected[headerKeyID] = recipient.publicKey.KeyID
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.nonceSource != nil {
|
||||
nonce, err := ctx.nonceSource.Nonce()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("square/go-jose: Error generating nonce: %v", err)
|
||||
}
|
||||
protected[headerNonce] = nonce
|
||||
}
|
||||
|
||||
for k, v := range ctx.extraHeaders {
|
||||
protected[k] = v
|
||||
}
|
||||
|
||||
serializedProtected := mustSerializeJSON(protected)
|
||||
|
||||
input := []byte(fmt.Sprintf("%s.%s",
|
||||
base64.RawURLEncoding.EncodeToString(serializedProtected),
|
||||
base64.RawURLEncoding.EncodeToString(payload)))
|
||||
|
||||
signatureInfo, err := recipient.signer.signPayload(input, recipient.sigAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signatureInfo.protected = &rawHeader{}
|
||||
for k, v := range protected {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("square/go-jose: Error marshalling item %#v: %v", k, err)
|
||||
}
|
||||
(*signatureInfo.protected)[k] = makeRawMessage(b)
|
||||
}
|
||||
obj.Signatures[i] = signatureInfo
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func (ctx *genericSigner) Options() SignerOptions {
|
||||
return SignerOptions{
|
||||
NonceSource: ctx.nonceSource,
|
||||
EmbedJWK: ctx.embedJWK,
|
||||
ExtraHeaders: ctx.extraHeaders,
|
||||
}
|
||||
}
|
||||
|
||||
// Verify validates the signature on the object and returns the payload.
|
||||
// This function does not support multi-signature, if you desire multi-sig
|
||||
// verification use VerifyMulti instead.
|
||||
//
|
||||
// Be careful when verifying signatures based on embedded JWKs inside the
|
||||
// payload header. You cannot assume that the key received in a payload is
|
||||
// trusted.
|
||||
func (obj JSONWebSignature) Verify(verificationKey interface{}) ([]byte, error) {
|
||||
verifier, err := newVerifier(verificationKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(obj.Signatures) > 1 {
|
||||
return nil, errors.New("square/go-jose: too many signatures in payload; expecting only one")
|
||||
}
|
||||
|
||||
signature := obj.Signatures[0]
|
||||
headers := signature.mergedHeaders()
|
||||
critical, err := headers.getCritical()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(critical) > 0 {
|
||||
// Unsupported crit header
|
||||
return nil, ErrCryptoFailure
|
||||
}
|
||||
|
||||
input := obj.computeAuthData(&signature)
|
||||
alg := headers.getSignatureAlgorithm()
|
||||
err = verifier.verifyPayload(input, signature.Signature, alg)
|
||||
if err == nil {
|
||||
return obj.payload, nil
|
||||
}
|
||||
|
||||
return nil, ErrCryptoFailure
|
||||
}
|
||||
|
||||
// VerifyMulti validates (one of the multiple) signatures on the object and
|
||||
// returns the index of the signature that was verified, along with the signature
|
||||
// object and the payload. We return the signature and index to guarantee that
|
||||
// callers are getting the verified value.
|
||||
func (obj JSONWebSignature) VerifyMulti(verificationKey interface{}) (int, Signature, []byte, error) {
|
||||
verifier, err := newVerifier(verificationKey)
|
||||
if err != nil {
|
||||
return -1, Signature{}, nil, err
|
||||
}
|
||||
|
||||
for i, signature := range obj.Signatures {
|
||||
headers := signature.mergedHeaders()
|
||||
critical, err := headers.getCritical()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if len(critical) > 0 {
|
||||
// Unsupported crit header
|
||||
continue
|
||||
}
|
||||
|
||||
input := obj.computeAuthData(&signature)
|
||||
alg := headers.getSignatureAlgorithm()
|
||||
err = verifier.verifyPayload(input, signature.Signature, alg)
|
||||
if err == nil {
|
||||
return i, signature, obj.payload, nil
|
||||
}
|
||||
}
|
||||
|
||||
return -1, Signature{}, nil, ErrCryptoFailure
|
||||
}
|
||||
360
vendor/gopkg.in/square/go-jose.v2/symmetric.go
generated
vendored
Normal file
360
vendor/gopkg.in/square/go-jose.v2/symmetric.go
generated
vendored
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
/*-
|
||||
* Copyright 2014 Square Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jose
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
"gopkg.in/square/go-jose.v2/cipher"
|
||||
)
|
||||
|
||||
// Random reader (stubbed out in tests)
|
||||
var randReader = rand.Reader
|
||||
|
||||
// Dummy key cipher for shared symmetric key mode
|
||||
type symmetricKeyCipher struct {
|
||||
key []byte // Pre-shared content-encryption key
|
||||
}
|
||||
|
||||
// Signer/verifier for MAC modes
|
||||
type symmetricMac struct {
|
||||
key []byte
|
||||
}
|
||||
|
||||
// Input/output from an AEAD operation
|
||||
type aeadParts struct {
|
||||
iv, ciphertext, tag []byte
|
||||
}
|
||||
|
||||
// A content cipher based on an AEAD construction
|
||||
type aeadContentCipher struct {
|
||||
keyBytes int
|
||||
authtagBytes int
|
||||
getAead func(key []byte) (cipher.AEAD, error)
|
||||
}
|
||||
|
||||
// Random key generator
|
||||
type randomKeyGenerator struct {
|
||||
size int
|
||||
}
|
||||
|
||||
// Static key generator
|
||||
type staticKeyGenerator struct {
|
||||
key []byte
|
||||
}
|
||||
|
||||
// Create a new content cipher based on AES-GCM
|
||||
func newAESGCM(keySize int) contentCipher {
|
||||
return &aeadContentCipher{
|
||||
keyBytes: keySize,
|
||||
authtagBytes: 16,
|
||||
getAead: func(key []byte) (cipher.AEAD, error) {
|
||||
aes, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cipher.NewGCM(aes)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new content cipher based on AES-CBC+HMAC
|
||||
func newAESCBC(keySize int) contentCipher {
|
||||
return &aeadContentCipher{
|
||||
keyBytes: keySize * 2,
|
||||
authtagBytes: 16,
|
||||
getAead: func(key []byte) (cipher.AEAD, error) {
|
||||
return josecipher.NewCBCHMAC(key, aes.NewCipher)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Get an AEAD cipher object for the given content encryption algorithm
|
||||
func getContentCipher(alg ContentEncryption) contentCipher {
|
||||
switch alg {
|
||||
case A128GCM:
|
||||
return newAESGCM(16)
|
||||
case A192GCM:
|
||||
return newAESGCM(24)
|
||||
case A256GCM:
|
||||
return newAESGCM(32)
|
||||
case A128CBC_HS256:
|
||||
return newAESCBC(16)
|
||||
case A192CBC_HS384:
|
||||
return newAESCBC(24)
|
||||
case A256CBC_HS512:
|
||||
return newAESCBC(32)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// newSymmetricRecipient creates a JWE encrypter based on AES-GCM key wrap.
|
||||
func newSymmetricRecipient(keyAlg KeyAlgorithm, key []byte) (recipientKeyInfo, error) {
|
||||
switch keyAlg {
|
||||
case DIRECT, A128GCMKW, A192GCMKW, A256GCMKW, A128KW, A192KW, A256KW:
|
||||
default:
|
||||
return recipientKeyInfo{}, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
return recipientKeyInfo{
|
||||
keyAlg: keyAlg,
|
||||
keyEncrypter: &symmetricKeyCipher{
|
||||
key: key,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newSymmetricSigner creates a recipientSigInfo based on the given key.
|
||||
func newSymmetricSigner(sigAlg SignatureAlgorithm, key []byte) (recipientSigInfo, error) {
|
||||
// Verify that key management algorithm is supported by this encrypter
|
||||
switch sigAlg {
|
||||
case HS256, HS384, HS512:
|
||||
default:
|
||||
return recipientSigInfo{}, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
return recipientSigInfo{
|
||||
sigAlg: sigAlg,
|
||||
signer: &symmetricMac{
|
||||
key: key,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Generate a random key for the given content cipher
|
||||
func (ctx randomKeyGenerator) genKey() ([]byte, rawHeader, error) {
|
||||
key := make([]byte, ctx.size)
|
||||
_, err := io.ReadFull(randReader, key)
|
||||
if err != nil {
|
||||
return nil, rawHeader{}, err
|
||||
}
|
||||
|
||||
return key, rawHeader{}, nil
|
||||
}
|
||||
|
||||
// Key size for random generator
|
||||
func (ctx randomKeyGenerator) keySize() int {
|
||||
return ctx.size
|
||||
}
|
||||
|
||||
// Generate a static key (for direct mode)
|
||||
func (ctx staticKeyGenerator) genKey() ([]byte, rawHeader, error) {
|
||||
cek := make([]byte, len(ctx.key))
|
||||
copy(cek, ctx.key)
|
||||
return cek, rawHeader{}, nil
|
||||
}
|
||||
|
||||
// Key size for static generator
|
||||
func (ctx staticKeyGenerator) keySize() int {
|
||||
return len(ctx.key)
|
||||
}
|
||||
|
||||
// Get key size for this cipher
|
||||
func (ctx aeadContentCipher) keySize() int {
|
||||
return ctx.keyBytes
|
||||
}
|
||||
|
||||
// Encrypt some data
|
||||
func (ctx aeadContentCipher) encrypt(key, aad, pt []byte) (*aeadParts, error) {
|
||||
// Get a new AEAD instance
|
||||
aead, err := ctx.getAead(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize a new nonce
|
||||
iv := make([]byte, aead.NonceSize())
|
||||
_, err = io.ReadFull(randReader, iv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertextAndTag := aead.Seal(nil, iv, pt, aad)
|
||||
offset := len(ciphertextAndTag) - ctx.authtagBytes
|
||||
|
||||
return &aeadParts{
|
||||
iv: iv,
|
||||
ciphertext: ciphertextAndTag[:offset],
|
||||
tag: ciphertextAndTag[offset:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Decrypt some data
|
||||
func (ctx aeadContentCipher) decrypt(key, aad []byte, parts *aeadParts) ([]byte, error) {
|
||||
aead, err := ctx.getAead(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return aead.Open(nil, parts.iv, append(parts.ciphertext, parts.tag...), aad)
|
||||
}
|
||||
|
||||
// Encrypt the content encryption key.
|
||||
func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
|
||||
switch alg {
|
||||
case DIRECT:
|
||||
return recipientInfo{
|
||||
header: &rawHeader{},
|
||||
}, nil
|
||||
case A128GCMKW, A192GCMKW, A256GCMKW:
|
||||
aead := newAESGCM(len(ctx.key))
|
||||
|
||||
parts, err := aead.encrypt(ctx.key, []byte{}, cek)
|
||||
if err != nil {
|
||||
return recipientInfo{}, err
|
||||
}
|
||||
|
||||
header := &rawHeader{}
|
||||
header.set(headerIV, newBuffer(parts.iv))
|
||||
header.set(headerTag, newBuffer(parts.tag))
|
||||
|
||||
return recipientInfo{
|
||||
header: header,
|
||||
encryptedKey: parts.ciphertext,
|
||||
}, nil
|
||||
case A128KW, A192KW, A256KW:
|
||||
block, err := aes.NewCipher(ctx.key)
|
||||
if err != nil {
|
||||
return recipientInfo{}, err
|
||||
}
|
||||
|
||||
jek, err := josecipher.KeyWrap(block, cek)
|
||||
if err != nil {
|
||||
return recipientInfo{}, err
|
||||
}
|
||||
|
||||
return recipientInfo{
|
||||
encryptedKey: jek,
|
||||
header: &rawHeader{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return recipientInfo{}, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
// Decrypt the content encryption key.
|
||||
func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
|
||||
switch headers.getAlgorithm() {
|
||||
case DIRECT:
|
||||
cek := make([]byte, len(ctx.key))
|
||||
copy(cek, ctx.key)
|
||||
return cek, nil
|
||||
case A128GCMKW, A192GCMKW, A256GCMKW:
|
||||
aead := newAESGCM(len(ctx.key))
|
||||
|
||||
iv, err := headers.getIV()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid IV: %v", err)
|
||||
}
|
||||
tag, err := headers.getTag()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid tag: %v", err)
|
||||
}
|
||||
|
||||
parts := &aeadParts{
|
||||
iv: iv.bytes(),
|
||||
ciphertext: recipient.encryptedKey,
|
||||
tag: tag.bytes(),
|
||||
}
|
||||
|
||||
cek, err := aead.decrypt(ctx.key, []byte{}, parts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cek, nil
|
||||
case A128KW, A192KW, A256KW:
|
||||
block, err := aes.NewCipher(ctx.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cek, err := josecipher.KeyUnwrap(block, recipient.encryptedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cek, nil
|
||||
}
|
||||
|
||||
return nil, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
// Sign the given payload
|
||||
func (ctx symmetricMac) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
||||
mac, err := ctx.hmac(payload, alg)
|
||||
if err != nil {
|
||||
return Signature{}, errors.New("square/go-jose: failed to compute hmac")
|
||||
}
|
||||
|
||||
return Signature{
|
||||
Signature: mac,
|
||||
protected: &rawHeader{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Verify the given payload
|
||||
func (ctx symmetricMac) verifyPayload(payload []byte, mac []byte, alg SignatureAlgorithm) error {
|
||||
expected, err := ctx.hmac(payload, alg)
|
||||
if err != nil {
|
||||
return errors.New("square/go-jose: failed to compute hmac")
|
||||
}
|
||||
|
||||
if len(mac) != len(expected) {
|
||||
return errors.New("square/go-jose: invalid hmac")
|
||||
}
|
||||
|
||||
match := subtle.ConstantTimeCompare(mac, expected)
|
||||
if match != 1 {
|
||||
return errors.New("square/go-jose: invalid hmac")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compute the HMAC based on the given alg value
|
||||
func (ctx symmetricMac) hmac(payload []byte, alg SignatureAlgorithm) ([]byte, error) {
|
||||
var hash func() hash.Hash
|
||||
|
||||
switch alg {
|
||||
case HS256:
|
||||
hash = sha256.New
|
||||
case HS384:
|
||||
hash = sha512.New384
|
||||
case HS512:
|
||||
hash = sha512.New
|
||||
default:
|
||||
return nil, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
hmac := hmac.New(hash, ctx.key)
|
||||
|
||||
// According to documentation, Write() on hash never fails
|
||||
_, _ = hmac.Write(payload)
|
||||
return hmac.Sum(nil), nil
|
||||
}
|
||||
72
vendor/vendor.json
vendored
72
vendor/vendor.json
vendored
|
|
@ -21,38 +21,38 @@
|
|||
{
|
||||
"checksumSHA1": "AH7jcN7pvaPDU6UjHdpT081DDGk=",
|
||||
"path": "cloud.google.com/go/compute/metadata",
|
||||
"revision": "a0438a757b4fb67653a453baa844ac9630b4a4be",
|
||||
"revisionTime": "2017-09-15T02:54:18Z"
|
||||
"revision": "58d5b92c85a97c78b82bdcb43e8d192c4ab0a239",
|
||||
"revisionTime": "2017-09-08T12:01:57Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "/ixPd+hSgsbAjBI/fPqmHtTFRM8=",
|
||||
"path": "cloud.google.com/go/iam",
|
||||
"revision": "a0438a757b4fb67653a453baa844ac9630b4a4be",
|
||||
"revisionTime": "2017-09-15T02:54:18Z"
|
||||
"revision": "58d5b92c85a97c78b82bdcb43e8d192c4ab0a239",
|
||||
"revisionTime": "2017-09-08T12:01:57Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "vXPMGAHxvqWSpSFqqUfZBZS1dBo=",
|
||||
"path": "cloud.google.com/go/internal",
|
||||
"revision": "a0438a757b4fb67653a453baa844ac9630b4a4be",
|
||||
"revisionTime": "2017-09-15T02:54:18Z"
|
||||
"revision": "58d5b92c85a97c78b82bdcb43e8d192c4ab0a239",
|
||||
"revisionTime": "2017-09-08T12:01:57Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "MCns2LLZtUZEx6JWyYBrcbSuTXg=",
|
||||
"path": "cloud.google.com/go/internal/optional",
|
||||
"revision": "a0438a757b4fb67653a453baa844ac9630b4a4be",
|
||||
"revisionTime": "2017-09-15T02:54:18Z"
|
||||
"revision": "58d5b92c85a97c78b82bdcb43e8d192c4ab0a239",
|
||||
"revisionTime": "2017-09-08T12:01:57Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "P0DmgQq10tIKBxGrPwdOGXEGk0A=",
|
||||
"path": "cloud.google.com/go/internal/version",
|
||||
"revision": "a0438a757b4fb67653a453baa844ac9630b4a4be",
|
||||
"revisionTime": "2017-09-15T02:54:18Z"
|
||||
"revision": "58d5b92c85a97c78b82bdcb43e8d192c4ab0a239",
|
||||
"revisionTime": "2017-09-08T12:01:57Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "QHbGgo+9M+l/RmOV1EA2p/miarI=",
|
||||
"path": "cloud.google.com/go/storage",
|
||||
"revision": "a0438a757b4fb67653a453baa844ac9630b4a4be",
|
||||
"revisionTime": "2017-09-15T02:54:18Z"
|
||||
"revision": "58d5b92c85a97c78b82bdcb43e8d192c4ab0a239",
|
||||
"revisionTime": "2017-09-08T12:01:57Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "ayQzpLsnDmiaVeKWQeySiIvAjiA=",
|
||||
|
|
@ -1057,16 +1057,16 @@
|
|||
"revisionTime": "2017-09-02T04:25:43Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "zUnavZDfX7KQQ2s/hHKG6T3zzlc=",
|
||||
"checksumSHA1": "J/+RffHqbSdOeI82+ysAJruyO9w=",
|
||||
"path": "github.com/hashicorp/vault-plugin-auth-gcp/plugin",
|
||||
"revision": "e0d34474f22c41c835f156b42e398d719919245b",
|
||||
"revisionTime": "2017-09-15T04:14:02Z"
|
||||
"revision": "a807a8507e636e40403455258ed25954ec254cad",
|
||||
"revisionTime": "2017-09-15T19:03:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "2Fk5xIPOoKD4Of9cUIQPKXb2htg=",
|
||||
"checksumSHA1": "nrNcGdv/8Ut8ScFy3tQoY3dpQvs=",
|
||||
"path": "github.com/hashicorp/vault-plugin-auth-gcp/plugin/util",
|
||||
"revision": "e0d34474f22c41c835f156b42e398d719919245b",
|
||||
"revisionTime": "2017-09-15T04:14:02Z"
|
||||
"revision": "a807a8507e636e40403455258ed25954ec254cad",
|
||||
"revisionTime": "2017-09-15T19:03:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "ZhK6IO2XN81Y+3RAjTcVm1Ic7oU=",
|
||||
|
|
@ -1560,6 +1560,12 @@
|
|||
"revision": "1cbadb444a806fd9430d14ad08967ed91da4fa0a",
|
||||
"revisionTime": "2017-09-13T19:45:57Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "dzy9lJFRFg4ewapupNs5pbhJQxQ=",
|
||||
"path": "google.golang.org/api/compute/v1",
|
||||
"revision": "39c3dd417c5a443607650f18e829ad308da08dd2",
|
||||
"revisionTime": "2017-09-15T00:03:48Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "/y0saWnM+kTnSvZrNlvoNOgj0Uo=",
|
||||
"path": "google.golang.org/api/gensupport",
|
||||
|
|
@ -1602,6 +1608,12 @@
|
|||
"revision": "39c3dd417c5a443607650f18e829ad308da08dd2",
|
||||
"revisionTime": "2017-09-15T00:03:48Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "1D3XnZy4TWLBstH2IzOHZHLS9HA=",
|
||||
"path": "google.golang.org/api/oauth2/v2",
|
||||
"revision": "39c3dd417c5a443607650f18e829ad308da08dd2",
|
||||
"revisionTime": "2017-09-15T00:03:48Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Y3CG3ZFIYfF6AhvpiBMBAGcZMV4=",
|
||||
"path": "google.golang.org/api/option",
|
||||
|
|
@ -1866,6 +1878,30 @@
|
|||
"revision": "a7951f7a8442f0e70d36e499ed4d744f00af2963",
|
||||
"revisionTime": "2017-07-29T08:02:57Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Qq9iBb+/Hjnydxe0Ait/ouqX7yE=",
|
||||
"path": "gopkg.in/square/go-jose.v2",
|
||||
"revision": "f8f38de21b4dcd69d0413faf231983f5fd6634b1",
|
||||
"revisionTime": "2017-09-13T18:28:36Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Ho5sr2GbiR8S35IRni7vC54d5Js=",
|
||||
"path": "gopkg.in/square/go-jose.v2/cipher",
|
||||
"revision": "f8f38de21b4dcd69d0413faf231983f5fd6634b1",
|
||||
"revisionTime": "2017-09-13T18:28:36Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "JFun0lWY9eqd80Js2iWsehu1gc4=",
|
||||
"path": "gopkg.in/square/go-jose.v2/json",
|
||||
"revision": "f8f38de21b4dcd69d0413faf231983f5fd6634b1",
|
||||
"revisionTime": "2017-09-13T18:28:36Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "36YSMA8/CXxaomaM6bXrq1Gt7Rs=",
|
||||
"path": "gopkg.in/square/go-jose.v2/jwt",
|
||||
"revision": "f8f38de21b4dcd69d0413faf231983f5fd6634b1",
|
||||
"revisionTime": "2017-09-13T18:28:36Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "RDJpJQwkF012L6m/2BJizyOksNw=",
|
||||
"path": "gopkg.in/yaml.v2",
|
||||
|
|
|
|||
Loading…
Reference in a new issue