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

     1  /*
     2   * Copyright (c) 2016, 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 common
    21  
    22  import (
    23  	"bufio"
    24  	"bytes"
    25  	"compress/zlib"
    26  	"crypto"
    27  	"crypto/rand"
    28  	"crypto/rsa"
    29  	"crypto/sha256"
    30  	"crypto/x509"
    31  	"encoding/base64"
    32  	"encoding/json"
    33  	"io"
    34  	"io/ioutil"
    35  	"sync"
    36  
    37  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
    38  )
    39  
    40  // AuthenticatedDataPackage is a JSON record containing some Psiphon data
    41  // payload, such as list of Psiphon server entries. As it may be downloaded
    42  // from various sources, it is digitally signed so that the data may be
    43  // authenticated.
    44  type AuthenticatedDataPackage struct {
    45  	Data                   string `json:"data"`
    46  	SigningPublicKeyDigest []byte `json:"signingPublicKeyDigest"`
    47  	Signature              []byte `json:"signature"`
    48  }
    49  
    50  // GenerateAuthenticatedDataPackageKeys generates a key pair
    51  // be used to sign and verify AuthenticatedDataPackages.
    52  func GenerateAuthenticatedDataPackageKeys() (string, string, error) {
    53  
    54  	rsaKey, err := rsa.GenerateKey(rand.Reader, 4096)
    55  	if err != nil {
    56  		return "", "", errors.Trace(err)
    57  	}
    58  
    59  	publicKeyBytes, err := x509.MarshalPKIXPublicKey(rsaKey.Public())
    60  	if err != nil {
    61  		return "", "", errors.Trace(err)
    62  	}
    63  
    64  	privateKeyBytes := x509.MarshalPKCS1PrivateKey(rsaKey)
    65  
    66  	return base64.StdEncoding.EncodeToString(publicKeyBytes),
    67  		base64.StdEncoding.EncodeToString(privateKeyBytes),
    68  		nil
    69  }
    70  
    71  func sha256sum(data string) []byte {
    72  	digest := sha256.Sum256([]byte(data))
    73  	return digest[:]
    74  }
    75  
    76  // WriteAuthenticatedDataPackage creates an AuthenticatedDataPackage
    77  // containing the specified data and signed by the given key. The output
    78  // conforms with the legacy format here:
    79  // https://bitbucket.org/psiphon/psiphon-circumvention-system/src/c25d080f6827b141fe637050ce0d5bd0ae2e9db5/Automation/psi_ops_crypto_tools.py
    80  func WriteAuthenticatedDataPackage(
    81  	data string, signingPublicKey, signingPrivateKey string) ([]byte, error) {
    82  
    83  	derEncodedPrivateKey, err := base64.StdEncoding.DecodeString(signingPrivateKey)
    84  	if err != nil {
    85  		return nil, errors.Trace(err)
    86  	}
    87  	rsaPrivateKey, err := x509.ParsePKCS1PrivateKey(derEncodedPrivateKey)
    88  	if err != nil {
    89  		return nil, errors.Trace(err)
    90  	}
    91  
    92  	signature, err := rsa.SignPKCS1v15(
    93  		rand.Reader,
    94  		rsaPrivateKey,
    95  		crypto.SHA256,
    96  		sha256sum(data))
    97  	if err != nil {
    98  		return nil, errors.Trace(err)
    99  	}
   100  
   101  	packageJSON, err := json.Marshal(
   102  		&AuthenticatedDataPackage{
   103  			Data:                   data,
   104  			SigningPublicKeyDigest: sha256sum(signingPublicKey),
   105  			Signature:              signature,
   106  		})
   107  	if err != nil {
   108  		return nil, errors.Trace(err)
   109  	}
   110  
   111  	return Compress(packageJSON), nil
   112  }
   113  
   114  // ReadAuthenticatedDataPackage extracts and verifies authenticated
   115  // data from an AuthenticatedDataPackage. The package must have been
   116  // signed with the given key.
   117  //
   118  // Set isCompressed to false to read packages that are not compressed.
   119  func ReadAuthenticatedDataPackage(
   120  	dataPackage []byte, isCompressed bool, signingPublicKey string) (string, error) {
   121  
   122  	var packageJSON []byte
   123  	var err error
   124  
   125  	if isCompressed {
   126  		packageJSON, err = Decompress(dataPackage)
   127  		if err != nil {
   128  			return "", errors.Trace(err)
   129  		}
   130  	} else {
   131  		packageJSON = dataPackage
   132  	}
   133  
   134  	var authenticatedDataPackage *AuthenticatedDataPackage
   135  	err = json.Unmarshal(packageJSON, &authenticatedDataPackage)
   136  	if err != nil {
   137  		return "", errors.Trace(err)
   138  	}
   139  
   140  	derEncodedPublicKey, err := base64.StdEncoding.DecodeString(signingPublicKey)
   141  	if err != nil {
   142  		return "", errors.Trace(err)
   143  	}
   144  	publicKey, err := x509.ParsePKIXPublicKey(derEncodedPublicKey)
   145  	if err != nil {
   146  		return "", errors.Trace(err)
   147  	}
   148  	rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
   149  	if !ok {
   150  		return "", errors.TraceNew("unexpected signing public key type")
   151  	}
   152  
   153  	if !bytes.Equal(
   154  		authenticatedDataPackage.SigningPublicKeyDigest,
   155  		sha256sum(signingPublicKey)) {
   156  
   157  		return "", errors.TraceNew("unexpected signing public key digest")
   158  	}
   159  
   160  	err = rsa.VerifyPKCS1v15(
   161  		rsaPublicKey,
   162  		crypto.SHA256,
   163  		sha256sum(authenticatedDataPackage.Data),
   164  		authenticatedDataPackage.Signature)
   165  	if err != nil {
   166  		return "", errors.Trace(err)
   167  	}
   168  
   169  	return authenticatedDataPackage.Data, nil
   170  }
   171  
   172  // NewAuthenticatedDataPackageReader extracts and verifies authenticated
   173  // data from an AuthenticatedDataPackage stored in the specified file. The
   174  // package must have been signed with the given key.
   175  // NewAuthenticatedDataPackageReader does not load the entire package nor
   176  // the entire data into memory. It streams the package while verifying, and
   177  // returns an io.Reader that the caller may use to stream the authenticated
   178  // data payload.
   179  func NewAuthenticatedDataPackageReader(
   180  	dataPackage io.ReadSeeker, signingPublicKey string) (io.Reader, error) {
   181  
   182  	// The file is streamed in 2 passes. The first pass verifies the package
   183  	// signature. No payload data should be accepted/processed until the signature
   184  	// check is complete. The second pass repositions to the data payload and returns
   185  	// a reader the caller will use to stream the authenticated payload.
   186  	//
   187  	// Note: No exclusive file lock is held between passes, so it's possible to
   188  	// verify the data in one pass, and read different data in the second pass.
   189  	// For Psiphon's use cases, this will not happen in practise -- the packageFileName
   190  	// will not change while the returned io.Reader is used -- unless the client host
   191  	// is compromised; a compromised client host is outside of our threat model.
   192  
   193  	var payload io.Reader
   194  
   195  	for pass := 0; pass < 2; pass++ {
   196  
   197  		_, err := dataPackage.Seek(0, io.SeekStart)
   198  		if err != nil {
   199  			return nil, errors.Trace(err)
   200  		}
   201  
   202  		decompressor, err := zlib.NewReader(dataPackage)
   203  		if err != nil {
   204  			return nil, errors.Trace(err)
   205  		}
   206  		// TODO: need to Close decompressor to ensure zlib checksum is verified?
   207  
   208  		hash := sha256.New()
   209  
   210  		var jsonData io.Reader
   211  		var jsonSigningPublicKey []byte
   212  		var jsonSignature []byte
   213  
   214  		jsonReadBase64Value := func(value io.Reader) ([]byte, error) {
   215  			base64Value, err := ioutil.ReadAll(value)
   216  			if err != nil {
   217  				return nil, errors.Trace(err)
   218  			}
   219  			decodedValue, err := base64.StdEncoding.DecodeString(string(base64Value))
   220  			if err != nil {
   221  				return nil, errors.Trace(err)
   222  			}
   223  			return decodedValue, nil
   224  		}
   225  
   226  		jsonHandler := func(key string, value io.Reader) (bool, error) {
   227  			switch key {
   228  
   229  			case "data":
   230  				if pass == 0 {
   231  
   232  					_, err := io.Copy(hash, value)
   233  					if err != nil {
   234  						return false, errors.Trace(err)
   235  					}
   236  					return true, nil
   237  
   238  				} else { // pass == 1
   239  
   240  					jsonData = value
   241  
   242  					// The JSON stream parser must halt at this position,
   243  					// leaving the reader to be returned to the caller positioned
   244  					// at the start of the data payload.
   245  					return false, nil
   246  				}
   247  
   248  			case "signingPublicKeyDigest":
   249  				jsonSigningPublicKey, err = jsonReadBase64Value(value)
   250  				if err != nil {
   251  					return false, errors.Trace(err)
   252  				}
   253  				return true, nil
   254  
   255  			case "signature":
   256  				jsonSignature, err = jsonReadBase64Value(value)
   257  				if err != nil {
   258  					return false, errors.Trace(err)
   259  				}
   260  				return true, nil
   261  			}
   262  
   263  			return false, errors.Tracef("unexpected key '%s'", key)
   264  		}
   265  
   266  		// Using a buffered reader to consume zlib output in batches
   267  		// yields a significant speed up in the BenchmarkAuthenticatedPackage.
   268  		jsonStreamer := &limitedJSONStreamer{
   269  			reader:  bufio.NewReader(decompressor),
   270  			handler: jsonHandler,
   271  		}
   272  
   273  		err = jsonStreamer.Stream()
   274  		if err != nil {
   275  			return nil, errors.Trace(err)
   276  		}
   277  
   278  		if pass == 0 {
   279  
   280  			if jsonSigningPublicKey == nil || jsonSignature == nil {
   281  				return nil, errors.TraceNew("missing expected field")
   282  			}
   283  
   284  			derEncodedPublicKey, err := base64.StdEncoding.DecodeString(signingPublicKey)
   285  			if err != nil {
   286  				return nil, errors.Trace(err)
   287  			}
   288  			publicKey, err := x509.ParsePKIXPublicKey(derEncodedPublicKey)
   289  			if err != nil {
   290  				return nil, errors.Trace(err)
   291  			}
   292  			rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
   293  			if !ok {
   294  				return nil, errors.TraceNew("unexpected signing public key type")
   295  			}
   296  
   297  			if !bytes.Equal(jsonSigningPublicKey, sha256sum(signingPublicKey)) {
   298  				return nil, errors.TraceNew("unexpected signing public key digest")
   299  			}
   300  
   301  			err = rsa.VerifyPKCS1v15(
   302  				rsaPublicKey,
   303  				crypto.SHA256,
   304  				hash.Sum(nil),
   305  				jsonSignature)
   306  			if err != nil {
   307  				return nil, errors.Trace(err)
   308  			}
   309  
   310  		} else { // pass == 1
   311  
   312  			if jsonData == nil {
   313  				return nil, errors.TraceNew("missing expected field")
   314  			}
   315  
   316  			payload = jsonData
   317  		}
   318  	}
   319  
   320  	return payload, nil
   321  }
   322  
   323  // limitedJSONStreamer is a streaming JSON parser that supports just the
   324  // JSON required for the AuthenticatedDataPackage format and expected data payloads.
   325  //
   326  // Unlike other common streaming JSON parsers, limitedJSONStreamer streams the JSON
   327  // _values_, as the AuthenticatedDataPackage "data" value may be too large to fit into
   328  // memory.
   329  //
   330  // limitedJSONStreamer is not intended for use outside of AuthenticatedDataPackage
   331  // and supports only a small subset of JSON: one object with string values only,
   332  // no escaped characters, no nested objects, no arrays, no numbers, etc.
   333  //
   334  // limitedJSONStreamer does support any JSON spec (http://www.json.org/) format
   335  // for its limited subset. So, for example, any whitespace/formatting should be
   336  // supported and the creator of AuthenticatedDataPackage should be able to use
   337  // any valid JSON that results in a AuthenticatedDataPackage object.
   338  //
   339  // For each key/value pair, handler is invoked with the key name and a reader
   340  // to stream the value. The handler _must_ read value to EOF (or return an error).
   341  type limitedJSONStreamer struct {
   342  	reader  io.Reader
   343  	handler func(key string, value io.Reader) (bool, error)
   344  }
   345  
   346  const (
   347  	stateJSONSeekingObjectStart = iota
   348  	stateJSONSeekingKeyStart
   349  	stateJSONSeekingKeyEnd
   350  	stateJSONSeekingColon
   351  	stateJSONSeekingStringValueStart
   352  	stateJSONSeekingStringValueEnd
   353  	stateJSONSeekingNextPair
   354  	stateJSONObjectEnd
   355  )
   356  
   357  func (streamer *limitedJSONStreamer) Stream() error {
   358  
   359  	// TODO: validate that strings are valid Unicode?
   360  
   361  	isWhitespace := func(b byte) bool {
   362  		return b == ' ' || b == '\t' || b == '\r' || b == '\n'
   363  	}
   364  
   365  	nextByte := make([]byte, 1)
   366  	keyBuffer := new(bytes.Buffer)
   367  	state := stateJSONSeekingObjectStart
   368  
   369  	for {
   370  		n, readErr := streamer.reader.Read(nextByte)
   371  
   372  		if n > 0 {
   373  
   374  			b := nextByte[0]
   375  
   376  			switch state {
   377  
   378  			case stateJSONSeekingObjectStart:
   379  				if b == '{' {
   380  					state = stateJSONSeekingKeyStart
   381  				} else if !isWhitespace(b) {
   382  					return errors.Tracef("unexpected character %#U while seeking object start", b)
   383  				}
   384  
   385  			case stateJSONSeekingKeyStart:
   386  				if b == '"' {
   387  					state = stateJSONSeekingKeyEnd
   388  					keyBuffer.Reset()
   389  				} else if !isWhitespace(b) {
   390  					return errors.Tracef("unexpected character %#U while seeking key start", b)
   391  				}
   392  
   393  			case stateJSONSeekingKeyEnd:
   394  				if b == '\\' {
   395  					return errors.TraceNew("unsupported escaped character")
   396  				} else if b == '"' {
   397  					state = stateJSONSeekingColon
   398  				} else {
   399  					keyBuffer.WriteByte(b)
   400  				}
   401  
   402  			case stateJSONSeekingColon:
   403  				if b == ':' {
   404  					state = stateJSONSeekingStringValueStart
   405  				} else if !isWhitespace(b) {
   406  					return errors.Tracef("unexpected character %#U while seeking colon", b)
   407  				}
   408  
   409  			case stateJSONSeekingStringValueStart:
   410  				if b == '"' {
   411  					state = stateJSONSeekingStringValueEnd
   412  
   413  					key := keyBuffer.String()
   414  
   415  					// Wrap the main reader in a reader that will read up to the end
   416  					// of the value and then EOF. The handler is expected to consume
   417  					// the full value, and then stream parsing will resume after the
   418  					// end of the value.
   419  					valueStreamer := &limitedJSONValueStreamer{
   420  						reader: streamer.reader,
   421  					}
   422  
   423  					continueStreaming, err := streamer.handler(key, valueStreamer)
   424  					if err != nil {
   425  						return errors.Trace(err)
   426  					}
   427  
   428  					// The handler may request that streaming halt at this point; no
   429  					// further changes are made to streamer.reader, leaving the value
   430  					// exactly where the hander leaves it.
   431  					if !continueStreaming {
   432  						return nil
   433  					}
   434  
   435  					state = stateJSONSeekingNextPair
   436  
   437  				} else if !isWhitespace(b) {
   438  					return errors.Tracef("unexpected character %#U while seeking value start", b)
   439  				}
   440  
   441  			case stateJSONSeekingNextPair:
   442  				if b == ',' {
   443  					state = stateJSONSeekingKeyStart
   444  				} else if b == '}' {
   445  					state = stateJSONObjectEnd
   446  				} else if !isWhitespace(b) {
   447  					return errors.Tracef("unexpected character %#U while seeking next name/value pair", b)
   448  				}
   449  
   450  			case stateJSONObjectEnd:
   451  				if !isWhitespace(b) {
   452  					return errors.Tracef("unexpected character %#U after object end", b)
   453  				}
   454  
   455  			default:
   456  				return errors.TraceNew("unexpected state")
   457  
   458  			}
   459  		}
   460  
   461  		if readErr != nil {
   462  			if readErr == io.EOF {
   463  				if state != stateJSONObjectEnd {
   464  					return errors.TraceNew("unexpected EOF before object end")
   465  				}
   466  				return nil
   467  			}
   468  			return errors.Trace(readErr)
   469  		}
   470  	}
   471  }
   472  
   473  // limitedJSONValueStreamer wraps the limitedJSONStreamer reader
   474  // with a reader that reads to the end of a string value and then
   475  // terminates with EOF.
   476  type limitedJSONValueStreamer struct {
   477  	mutex  sync.Mutex
   478  	eof    bool
   479  	reader io.Reader
   480  }
   481  
   482  // Read implements the io.Reader interface.
   483  func (streamer *limitedJSONValueStreamer) Read(p []byte) (int, error) {
   484  	streamer.mutex.Lock()
   485  	defer streamer.mutex.Unlock()
   486  
   487  	if streamer.eof {
   488  		return 0, io.EOF
   489  	}
   490  
   491  	var i int
   492  	var err error
   493  
   494  	for i = 0; i < len(p); i++ {
   495  
   496  		var n int
   497  		n, err = streamer.reader.Read(p[i : i+1])
   498  
   499  		// process n == 1 before handling err, in case err is io.EOF
   500  		if n == 1 {
   501  			if p[i] == '"' {
   502  				streamer.eof = true
   503  				err = io.EOF
   504  			} else if p[i] == '\\' {
   505  				if err == nil {
   506  					// Psiphon server list string values contain '\n', so support
   507  					// that required case.
   508  					n, err = streamer.reader.Read(p[i : i+1])
   509  					if n == 1 && p[i] == 'n' {
   510  						p[i] = '\n'
   511  					} else {
   512  						err = errors.TraceNew("unsupported escaped character")
   513  					}
   514  				}
   515  			}
   516  		}
   517  
   518  		if err != nil {
   519  			break
   520  		}
   521  	}
   522  
   523  	return i, err
   524  }