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