github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/registration.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package apiserver
     5  
     6  import (
     7  	"crypto/rand"
     8  	"encoding/json"
     9  	"io/ioutil"
    10  	"net/http"
    11  
    12  	"github.com/juju/errors"
    13  	"golang.org/x/crypto/nacl/secretbox"
    14  	"gopkg.in/juju/names.v2"
    15  	"gopkg.in/macaroon-bakery.v1/httpbakery"
    16  	"gopkg.in/macaroon.v1"
    17  
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/state"
    20  )
    21  
    22  const (
    23  	secretboxNonceLength = 24
    24  	secretboxKeyLength   = 32
    25  )
    26  
    27  // registerUserHandler is an http.Handler for the "/register" endpoint. This is
    28  // used to complete a secure user registration process, and provide controller
    29  // login credentials.
    30  type registerUserHandler struct {
    31  	ctxt httpContext
    32  }
    33  
    34  // ServeHTTP implements the http.Handler interface.
    35  func (h *registerUserHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    36  	if req.Method != "POST" {
    37  		err := sendError(w, errors.MethodNotAllowedf("unsupported method: %q", req.Method))
    38  		if err != nil {
    39  			logger.Errorf("%v", err)
    40  		}
    41  		return
    42  	}
    43  	st, err := h.ctxt.stateForRequestUnauthenticated(req)
    44  	if err != nil {
    45  		if err := sendError(w, err); err != nil {
    46  			logger.Errorf("%v", err)
    47  		}
    48  		return
    49  	}
    50  	userTag, response, err := h.processPost(req, st)
    51  	if err != nil {
    52  		if err := sendError(w, err); err != nil {
    53  			logger.Errorf("%v", err)
    54  		}
    55  		return
    56  	}
    57  
    58  	// Set a short-lived macaroon as a cookie on the response,
    59  	// which the client can use to obtain a discharge macaroon.
    60  	m, err := h.ctxt.srv.authCtxt.CreateLocalLoginMacaroon(userTag)
    61  	if err != nil {
    62  		if err := sendError(w, err); err != nil {
    63  			logger.Errorf("%v", err)
    64  		}
    65  		return
    66  	}
    67  	cookie, err := httpbakery.NewCookie(macaroon.Slice{m})
    68  	if err != nil {
    69  		if err := sendError(w, err); err != nil {
    70  			logger.Errorf("%v", err)
    71  		}
    72  		return
    73  	}
    74  	http.SetCookie(w, cookie)
    75  
    76  	if err := sendStatusAndJSON(w, http.StatusOK, response); err != nil {
    77  		logger.Errorf("%v", err)
    78  	}
    79  }
    80  
    81  // The client will POST to the "/register" endpoint with a JSON-encoded
    82  // params.SecretKeyLoginRequest. This contains the tag of the user they
    83  // are registering, a (supposedly) unique nonce, and a ciphertext which
    84  // is the result of concatenating the user and nonce values, and then
    85  // encrypting and authenticating them with the NaCl Secretbox algorithm.
    86  //
    87  // If the server can decrypt the ciphertext, then it knows the client
    88  // has the required secret key; thus they are authenticated. The client
    89  // does not have the CA certificate for communicating securely with the
    90  // server, and so must also authenticate the server. The server will
    91  // similarly generate a unique nonce and encrypt the response payload
    92  // using the same secret key as the client. If the client can decrypt
    93  // the payload, it knows the server has the required secret key; thus
    94  // it is also authenticated.
    95  //
    96  // NOTE(axw) it is important that the client and server choose their
    97  // own nonces, because reusing a nonce means that the key-stream can
    98  // be revealed.
    99  func (h *registerUserHandler) processPost(req *http.Request, st *state.State) (
   100  	names.UserTag, *params.SecretKeyLoginResponse, error,
   101  ) {
   102  
   103  	failure := func(err error) (names.UserTag, *params.SecretKeyLoginResponse, error) {
   104  		return names.UserTag{}, nil, err
   105  	}
   106  
   107  	data, err := ioutil.ReadAll(req.Body)
   108  	if err != nil {
   109  		return failure(err)
   110  	}
   111  	var loginRequest params.SecretKeyLoginRequest
   112  	if err := json.Unmarshal(data, &loginRequest); err != nil {
   113  		return failure(err)
   114  	}
   115  
   116  	// Basic validation: ensure that the request contains a valid user tag,
   117  	// nonce, and ciphertext of the expected length.
   118  	userTag, err := names.ParseUserTag(loginRequest.User)
   119  	if err != nil {
   120  		return failure(err)
   121  	}
   122  	if len(loginRequest.Nonce) != secretboxNonceLength {
   123  		return failure(errors.NotValidf("nonce"))
   124  	}
   125  
   126  	// Decrypt the ciphertext with the user's secret key (if it has one).
   127  	user, err := st.User(userTag)
   128  	if err != nil {
   129  		return failure(err)
   130  	}
   131  	if len(user.SecretKey()) != secretboxKeyLength {
   132  		return failure(errors.NotFoundf("secret key for user %q", user.Name()))
   133  	}
   134  	var key [secretboxKeyLength]byte
   135  	var nonce [secretboxNonceLength]byte
   136  	copy(key[:], user.SecretKey())
   137  	copy(nonce[:], loginRequest.Nonce)
   138  	payloadBytes, ok := secretbox.Open(nil, loginRequest.PayloadCiphertext, &nonce, &key)
   139  	if !ok {
   140  		// Cannot decrypt the ciphertext, which implies that the secret
   141  		// key specified by the client is invalid.
   142  		return failure(errors.NotValidf("secret key"))
   143  	}
   144  
   145  	// Unmarshal the request payload, which contains the new password to
   146  	// set for the user.
   147  	var requestPayload params.SecretKeyLoginRequestPayload
   148  	if err := json.Unmarshal(payloadBytes, &requestPayload); err != nil {
   149  		return failure(errors.Annotate(err, "cannot unmarshal payload"))
   150  	}
   151  	if err := user.SetPassword(requestPayload.Password); err != nil {
   152  		return failure(errors.Annotate(err, "setting new password"))
   153  	}
   154  
   155  	// Respond with the CA-cert and password, encrypted again with the
   156  	// secret key.
   157  	responsePayload, err := h.getSecretKeyLoginResponsePayload(st, userTag)
   158  	if err != nil {
   159  		return failure(errors.Trace(err))
   160  	}
   161  	payloadBytes, err = json.Marshal(responsePayload)
   162  	if err != nil {
   163  		return failure(errors.Trace(err))
   164  	}
   165  	if _, err := rand.Read(nonce[:]); err != nil {
   166  		return failure(errors.Trace(err))
   167  	}
   168  	response := &params.SecretKeyLoginResponse{
   169  		Nonce:             nonce[:],
   170  		PayloadCiphertext: secretbox.Seal(nil, payloadBytes, &nonce, &key),
   171  	}
   172  	return userTag, response, nil
   173  }
   174  
   175  // getSecretKeyLoginResponsePayload returns the information required by the
   176  // client to login to the controller securely.
   177  func (h *registerUserHandler) getSecretKeyLoginResponsePayload(
   178  	st *state.State, userTag names.UserTag,
   179  ) (*params.SecretKeyLoginResponsePayload, error) {
   180  	if !st.IsController() {
   181  		return nil, errors.New("state is not for a controller")
   182  	}
   183  	payload := params.SecretKeyLoginResponsePayload{
   184  		CACert:         st.CACert(),
   185  		ControllerUUID: st.ControllerUUID(),
   186  	}
   187  	return &payload, nil
   188  }