github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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  	"gopkg.in/macaroon.v1"
    13  
    14  	"github.com/juju/errors"
    15  	"github.com/juju/names"
    16  	"golang.org/x/crypto/nacl/secretbox"
    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  	createLocalLoginMacaroon func(names.UserTag) (*macaroon.Macaroon, error)
    33  }
    34  
    35  // ServeHTTP implements the http.Handler interface.
    36  func (h *registerUserHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    37  	if req.Method != "POST" {
    38  		sendError(w, errors.MethodNotAllowedf("unsupported method: %q", req.Method))
    39  		return
    40  	}
    41  	st, err := h.ctxt.stateForRequestUnauthenticated(req)
    42  	if err != nil {
    43  		sendError(w, err)
    44  		return
    45  	}
    46  	response, err := h.processPost(req, st)
    47  	if err != nil {
    48  		sendError(w, err)
    49  		return
    50  	}
    51  	sendStatusAndJSON(w, http.StatusOK, response)
    52  }
    53  
    54  // The client will POST to the "/register" endpoint with a JSON-encoded
    55  // params.SecretKeyLoginRequest. This contains the tag of the user they
    56  // are registering, a (supposedly) unique nonce, and a ciphertext which
    57  // is the result of concatenating the user and nonce values, and then
    58  // encrypting and authenticating them with the NaCl Secretbox algorithm.
    59  //
    60  // If the server can decrypt the ciphertext, then it knows the client
    61  // has the required secret key; thus they are authenticated. The client
    62  // does not have the CA certificate for communicating securely with the
    63  // server, and so must also authenticate the server. The server will
    64  // similarly generate a unique nonce and encrypt the response payload
    65  // using the same secret key as the client. If the client can decrypt
    66  // the payload, it knows the server has the required secret key; thus
    67  // it is also authenticated.
    68  //
    69  // NOTE(axw) it is important that the client and server choose their
    70  // own nonces, because reusing a nonce means that the key-stream can
    71  // be revealed.
    72  func (h *registerUserHandler) processPost(req *http.Request, st *state.State) (*params.SecretKeyLoginResponse, error) {
    73  
    74  	data, err := ioutil.ReadAll(req.Body)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	var loginRequest params.SecretKeyLoginRequest
    79  	if err := json.Unmarshal(data, &loginRequest); err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	// Basic validation: ensure that the request contains a valid user tag,
    84  	// nonce, and ciphertext of the expected length.
    85  	userTag, err := names.ParseUserTag(loginRequest.User)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	if len(loginRequest.Nonce) != secretboxNonceLength {
    90  		return nil, errors.NotValidf("nonce")
    91  	}
    92  
    93  	// Decrypt the ciphertext with the user's secret key (if it has one).
    94  	user, err := st.User(userTag)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  	if len(user.SecretKey()) != secretboxKeyLength {
    99  		return nil, errors.NotFoundf("secret key for user %q", user.Name())
   100  	}
   101  	var key [secretboxKeyLength]byte
   102  	var nonce [secretboxNonceLength]byte
   103  	copy(key[:], user.SecretKey())
   104  	copy(nonce[:], loginRequest.Nonce)
   105  	payloadBytes, ok := secretbox.Open(nil, loginRequest.PayloadCiphertext, &nonce, &key)
   106  	if !ok {
   107  		// Cannot decrypt the ciphertext, which implies that the secret
   108  		// key specified by the client is invalid.
   109  		return nil, errors.NotValidf("secret key")
   110  	}
   111  
   112  	// Unmarshal the request payload, which contains the new password to
   113  	// set for the user.
   114  	var requestPayload params.SecretKeyLoginRequestPayload
   115  	if err := json.Unmarshal(payloadBytes, &requestPayload); err != nil {
   116  		return nil, errors.Annotate(err, "cannot unmarshal payload")
   117  	}
   118  	if err := user.SetPassword(requestPayload.Password); err != nil {
   119  		return nil, errors.Annotate(err, "setting new password")
   120  	}
   121  
   122  	// Respond with the CA-cert and password, encrypted again with the
   123  	// secret key.
   124  	responsePayload, err := h.getSecretKeyLoginResponsePayload(st, userTag)
   125  	if err != nil {
   126  		return nil, errors.Trace(err)
   127  	}
   128  	payloadBytes, err = json.Marshal(responsePayload)
   129  	if err != nil {
   130  		return nil, errors.Trace(err)
   131  	}
   132  	if _, err := rand.Read(nonce[:]); err != nil {
   133  		return nil, errors.Trace(err)
   134  	}
   135  	response := &params.SecretKeyLoginResponse{
   136  		Nonce:             nonce[:],
   137  		PayloadCiphertext: secretbox.Seal(nil, payloadBytes, &nonce, &key),
   138  	}
   139  	return response, nil
   140  }
   141  
   142  // getSecretKeyLoginResponsePayload returns the information required by the
   143  // client to login to the controller securely.
   144  func (h *registerUserHandler) getSecretKeyLoginResponsePayload(
   145  	st *state.State, userTag names.UserTag,
   146  ) (*params.SecretKeyLoginResponsePayload, error) {
   147  	if !st.IsController() {
   148  		return nil, errors.New("state is not for a controller")
   149  	}
   150  	mac, err := h.createLocalLoginMacaroon(userTag)
   151  	if err != nil {
   152  		return nil, errors.Trace(err)
   153  	}
   154  	payload := params.SecretKeyLoginResponsePayload{
   155  		CACert:         st.CACert(),
   156  		ControllerUUID: st.ModelUUID(),
   157  		Macaroon:       mac,
   158  	}
   159  	return &payload, nil
   160  }