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 := ¶ms.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 }