github.com/Psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/accesscontrol/accesscontrol.go (about)

     1  /*
     2   * Copyright (c) 2018, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  // Package accesscontrol implements an access control authorization scheme
    21  // based on digital signatures.
    22  //
    23  // Authorizations for specified access types are issued by an entity that
    24  // digitally signs each authorization. The digital signature is verified
    25  // by service providers before granting the specified access type. Each
    26  // authorization includes an expiry date and a unique ID that may be used
    27  // to mitigate malicious reuse/sharing of authorizations.
    28  //
    29  // In a typical deployment, the signing keys will be present on issuing
    30  // entities which are distinct from service providers. Only verification
    31  // keys will be deployed to service providers.
    32  //
    33  // An authorization is represented in JSON, which is then base64-encoded
    34  // for transport:
    35  //
    36  // {
    37  //   "Authorization" : {
    38  // 	 "ID" : <derived unique ID>,
    39  // 	 "AccessType" : <access type name; e.g., "my-access">,
    40  // 	 "Expires" : <RFC3339-encoded UTC time value>
    41  //   },
    42  //   "SigningKeyID" : <unique key ID>,
    43  //   "Signature" : <Ed25519 digital signature>
    44  // }
    45  //
    46  package accesscontrol
    47  
    48  import (
    49  	"crypto/ed25519"
    50  	"crypto/rand"
    51  	"crypto/sha256"
    52  	"crypto/subtle"
    53  	"encoding/base64"
    54  	"encoding/json"
    55  	"io"
    56  	"time"
    57  
    58  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
    59  	"golang.org/x/crypto/hkdf"
    60  )
    61  
    62  const (
    63  	keyIDLength              = 32
    64  	authorizationIDKeyLength = 32
    65  	authorizationIDLength    = 32
    66  )
    67  
    68  // SigningKey is the private key used to sign newly issued
    69  // authorizations for the specified access type. The key ID
    70  // is included in authorizations and identifies the
    71  // corresponding verification keys.
    72  //
    73  // AuthorizationIDKey is used to produce a unique
    74  // authentication ID that cannot be mapped back to its seed
    75  // value.
    76  type SigningKey struct {
    77  	ID                 []byte
    78  	AccessType         string
    79  	AuthorizationIDKey []byte
    80  	PrivateKey         []byte
    81  }
    82  
    83  // VerificationKey is the public key used to verify signed
    84  // authentications issued for the specified access type. The
    85  // authorization references the expected public key by ID.
    86  type VerificationKey struct {
    87  	ID         []byte
    88  	AccessType string
    89  	PublicKey  []byte
    90  }
    91  
    92  // NewKeyPair generates a new authorization signing key pair.
    93  func NewKeyPair(
    94  	accessType string) (*SigningKey, *VerificationKey, error) {
    95  
    96  	ID := make([]byte, keyIDLength)
    97  	_, err := rand.Read(ID)
    98  	if err != nil {
    99  		return nil, nil, errors.Trace(err)
   100  	}
   101  
   102  	authorizationIDKey := make([]byte, authorizationIDKeyLength)
   103  	_, err = rand.Read(authorizationIDKey)
   104  	if err != nil {
   105  		return nil, nil, errors.Trace(err)
   106  	}
   107  
   108  	publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
   109  	if err != nil {
   110  		return nil, nil, errors.Trace(err)
   111  	}
   112  
   113  	signingKey := &SigningKey{
   114  		ID:                 ID,
   115  		AccessType:         accessType,
   116  		AuthorizationIDKey: authorizationIDKey,
   117  		PrivateKey:         privateKey,
   118  	}
   119  
   120  	verificationKey := &VerificationKey{
   121  		ID:         ID,
   122  		AccessType: accessType,
   123  		PublicKey:  publicKey,
   124  	}
   125  
   126  	return signingKey, verificationKey, nil
   127  }
   128  
   129  // Authorization describes an authorization, with a unique ID,
   130  // granting access to a specified access type, and expiring at
   131  // the specified time.
   132  //
   133  // An Authorization is embedded within a digitally signed
   134  // object. This wrapping object adds a signature and a signing
   135  // key ID.
   136  type Authorization struct {
   137  	ID         []byte
   138  	AccessType string
   139  	Expires    time.Time
   140  }
   141  
   142  type signedAuthorization struct {
   143  	Authorization json.RawMessage
   144  	SigningKeyID  []byte
   145  	Signature     []byte
   146  }
   147  
   148  // ValidateSigningKey checks that a signing key is correctly configured.
   149  func ValidateSigningKey(signingKey *SigningKey) error {
   150  	if len(signingKey.ID) != keyIDLength ||
   151  		len(signingKey.AccessType) < 1 ||
   152  		len(signingKey.AuthorizationIDKey) != authorizationIDKeyLength ||
   153  		len(signingKey.PrivateKey) != ed25519.PrivateKeySize {
   154  		return errors.TraceNew("invalid signing key")
   155  	}
   156  	return nil
   157  }
   158  
   159  // IssueAuthorization issues an authorization signed with the
   160  // specified signing key.
   161  //
   162  // seedAuthorizationID should be a value that uniquely identifies
   163  // the purchase, subscription, or transaction that backs the
   164  // authorization; a distinct unique authorization ID will be derived
   165  // from the seed without revealing the original value. The authorization
   166  // ID is to be used to mitigate malicious authorization reuse/sharing.
   167  //
   168  // The first return value is a base64-encoded, serialized JSON representation
   169  // of the signed authorization that can be passed to VerifyAuthorization. The
   170  // second return value is the unique ID of the signed authorization returned in
   171  // the first value.
   172  func IssueAuthorization(
   173  	signingKey *SigningKey,
   174  	seedAuthorizationID []byte,
   175  	expires time.Time) (string, []byte, error) {
   176  
   177  	err := ValidateSigningKey(signingKey)
   178  	if err != nil {
   179  		return "", nil, errors.Trace(err)
   180  	}
   181  
   182  	hkdf := hkdf.New(sha256.New, signingKey.AuthorizationIDKey, nil, seedAuthorizationID)
   183  	ID := make([]byte, authorizationIDLength)
   184  	_, err = io.ReadFull(hkdf, ID)
   185  	if err != nil {
   186  		return "", nil, errors.Trace(err)
   187  	}
   188  
   189  	auth := Authorization{
   190  		ID:         ID,
   191  		AccessType: signingKey.AccessType,
   192  		Expires:    expires.UTC(),
   193  	}
   194  
   195  	authJSON, err := json.Marshal(auth)
   196  	if err != nil {
   197  		return "", nil, errors.Trace(err)
   198  	}
   199  
   200  	signature := ed25519.Sign(signingKey.PrivateKey, authJSON)
   201  
   202  	signedAuth := signedAuthorization{
   203  		Authorization: authJSON,
   204  		SigningKeyID:  signingKey.ID,
   205  		Signature:     signature,
   206  	}
   207  
   208  	signedAuthJSON, err := json.Marshal(signedAuth)
   209  	if err != nil {
   210  		return "", nil, errors.Trace(err)
   211  	}
   212  
   213  	encodedSignedAuth := base64.StdEncoding.EncodeToString(signedAuthJSON)
   214  
   215  	return encodedSignedAuth, ID, nil
   216  }
   217  
   218  // VerificationKeyRing is a set of verification keys to be deployed
   219  // to a service provider for verifying access authorizations.
   220  type VerificationKeyRing struct {
   221  	Keys []*VerificationKey
   222  }
   223  
   224  // ValidateVerificationKeyRing checks that a verification key ring is
   225  // correctly configured.
   226  func ValidateVerificationKeyRing(keyRing *VerificationKeyRing) error {
   227  	for _, key := range keyRing.Keys {
   228  		if len(key.ID) != keyIDLength ||
   229  			len(key.AccessType) < 1 ||
   230  			len(key.PublicKey) != ed25519.PublicKeySize {
   231  			return errors.TraceNew("invalid verification key")
   232  		}
   233  	}
   234  	return nil
   235  }
   236  
   237  // VerifyAuthorization verifies the signed authorization and, when
   238  // verified, returns the embedded Authorization struct with the
   239  // access control information.
   240  //
   241  // The key ID in the signed authorization is used to select the
   242  // appropriate verification key from the key ring.
   243  func VerifyAuthorization(
   244  	keyRing *VerificationKeyRing,
   245  	encodedSignedAuthorization string) (*Authorization, error) {
   246  
   247  	err := ValidateVerificationKeyRing(keyRing)
   248  	if err != nil {
   249  		return nil, errors.Trace(err)
   250  	}
   251  
   252  	signedAuthorizationJSON, err := base64.StdEncoding.DecodeString(
   253  		encodedSignedAuthorization)
   254  	if err != nil {
   255  		return nil, errors.Trace(err)
   256  	}
   257  
   258  	var signedAuth signedAuthorization
   259  	err = json.Unmarshal(signedAuthorizationJSON, &signedAuth)
   260  	if err != nil {
   261  		return nil, errors.Trace(err)
   262  	}
   263  
   264  	if len(signedAuth.SigningKeyID) != keyIDLength {
   265  		return nil, errors.TraceNew("invalid key ID length")
   266  	}
   267  
   268  	if len(signedAuth.Signature) != ed25519.SignatureSize {
   269  		return nil, errors.TraceNew("invalid signature length")
   270  	}
   271  
   272  	var verificationKey *VerificationKey
   273  
   274  	for _, key := range keyRing.Keys {
   275  		if subtle.ConstantTimeCompare(signedAuth.SigningKeyID, key.ID) == 1 {
   276  			verificationKey = key
   277  		}
   278  	}
   279  
   280  	if verificationKey == nil {
   281  		return nil, errors.TraceNew("invalid key ID")
   282  	}
   283  
   284  	if !ed25519.Verify(
   285  		verificationKey.PublicKey, signedAuth.Authorization, signedAuth.Signature) {
   286  		return nil, errors.TraceNew("invalid signature")
   287  	}
   288  
   289  	var auth Authorization
   290  
   291  	err = json.Unmarshal(signedAuth.Authorization, &auth)
   292  	if err != nil {
   293  		return nil, errors.Trace(err)
   294  	}
   295  
   296  	if len(auth.ID) == 0 {
   297  		return nil, errors.TraceNew("invalid authorization ID")
   298  	}
   299  
   300  	if auth.AccessType != verificationKey.AccessType {
   301  		return nil, errors.TraceNew("invalid access type")
   302  	}
   303  
   304  	if auth.Expires.IsZero() {
   305  		return nil, errors.TraceNew("invalid expiry")
   306  	}
   307  
   308  	if auth.Expires.Before(time.Now().UTC()) {
   309  		return nil, errors.TraceNew("expired authorization")
   310  	}
   311  
   312  	return &auth, nil
   313  }