diff --git a/api/authdata.go b/api/authdata.go new file mode 100644 index 0000000..ad2d180 --- /dev/null +++ b/api/authdata.go @@ -0,0 +1,194 @@ +package api + +import ( + "crypto/rsa" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/Usable-Security-and-Privacy-Lab/lets-auth-ca/certs" + "github.com/Usable-Security-and-Privacy-Lab/lets-auth-ca/models" + "github.com/Usable-Security-and-Privacy-Lab/lets-auth-ca/util" + "github.com/google/uuid" + "github.com/gorilla/mux" +) + +type authenticationDataRequest struct { + authenticatorCertificate string `json:"authenticatorCertificate"` +} + +type authenticationValutRequest struct { + authenticatorCertificate string `json:"authenticatorCertificate"` + authenticationData string `json:"authenticationData"` + lockIdentifier string `json:"lockIdentifier"` +} + +type entry struct { + accountName string + accountID uint + sessionPublicKey *rsa.PublicKey + sessionPrivateKey *rsa.PrivateKey +} + +type session struct { + sessionCertificate string + geoLocation string +} + +type sessionList struct { + authenticatorName string + sessions []session +} + +type AuthenticationDataResponse struct { +} + +func AuthDataRetrieval(w http.ResponseWriter, r *http.Request) { + fmt.Printf("Started AuthenticationData request %s\n", r.RequestURI) + defer fmt.Printf("Finished AuthenticationData request %s\n", r.RequestURI) + + vars := mux.Vars(r) + username, ok := vars["username"] + if !ok { + jsonResponse(w, fmt.Errorf("must supply a valid username i.e. foo@bar.com"), http.StatusBadRequest) + return + } + + reqBody, _ := ioutil.ReadAll(r.Body) + var request authenticationDataRequest + json.Unmarshal(reqBody, &request) + + authenticatorCertificate := request.authenticatorCertificate + + decryptedBytes, err := certs.DecodeAuthCert(authenticatorCertificate) + + if err != nil { + fmt.Printf("Error in decoding authCert") + w.WriteHeader(http.StatusUnauthorized) + return + } + + fmt.Print(username) + fmt.Print(decryptedBytes) + + // E. Relying Party Authentication + // TODO: authenticatorName from authenticatorCertificate (authenticatorList = [authenticatorName]) + + /* + create map(domain) = {"domain": {accountName, accountID, sessionPublicKey, sessionPrivateKey}} + sessionList = [authenticatorName, [sessionCertificate, geoLocation]] + + {E(PBKDF(masterPassword), symmetricKey) | + E(symmetricKey, [authenticatorName] | + {"domain": {accountName, accountID, sessionPublicKey, sessionPrivateKey})} + */ + + cfg := util.GetConfig() + + var domainMap = make(map[string]entry) + + userObj, err := models.GetUserByUsername(username) + + if err != nil { + // user isn't in database + fmt.Printf("User is not in database") + w.WriteHeader(http.StatusUnauthorized) + return + } + + domainMap[cfg.RPID] = entry{ + accountName: username, + accountID: userObj.ID, + sessionPublicKey: cfg.PublicKey, + sessionPrivateKey: cfg.PrivateKey, + } + +} + +// What will be the difference between POST and PUT in here? +// getting a lock +func AuthDataObtainLock(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + username, ok := vars["username"] + if !ok { + jsonResponse(w, fmt.Errorf("must supply a valid username i.e. foo@bar.com"), http.StatusBadRequest) + return + } + + reqBody, _ := ioutil.ReadAll(r.Body) + var request authenticationDataRequest + json.Unmarshal(reqBody, &request) + + authenticatorCertificate := request.authenticatorCertificate + fmt.Printf(authenticatorCertificate) + + // TODO: validate authenticator certificate. If it fails, it will give 403 error + + user, err := models.GetUserByUsername(username) + if err != nil { + // user isn't in database + fmt.Printf("User is not in database") + w.WriteHeader(http.StatusUnauthorized) + return + } + + if user.IsLocked { + fmt.Printf("already obtained the lock") + w.WriteHeader(http.StatusConflict) + } + + user.IsLocked = true + err = models.UpdateUser(&user) + if err != nil { + fmt.Printf("failed to obtain lock") + w.WriteHeader(http.StatusInternalServerError) + } + + var dataLock = models.DataLock{ + UserID: user.ID, + LockIdentifier: uuid.New(), + } + + err = models.CreateDataLock(&dataLock) + if err != nil { + fmt.Printf("failed to obtain lock") + w.WriteHeader(http.StatusInternalServerError) + } +} + +// store the vault +func AuthDataStoreVault(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + username, ok := vars["username"] + if !ok { + jsonResponse(w, fmt.Errorf("must supply a valid username i.e. foo@bar.com"), http.StatusBadRequest) + return + } + + reqBody, _ := ioutil.ReadAll(r.Body) + var request authenticationValutRequest + json.Unmarshal(reqBody, &request) + + authenticatorCertificate := request.authenticatorCertificate + fmt.Printf(authenticatorCertificate) + + user, err := models.GetUserByUsername(username) + if err != nil { + // user isn't in database + fmt.Printf("User is not in database") + w.WriteHeader(http.StatusUnauthorized) + return + } + + lock, err := models.GetLockByUserID(user.ID) + if err != nil { + fmt.Printf("Cannot find lock") + w.WriteHeader(http.StatusInternalServerError) + } + + if lock.LockIdentifier != uuid.MustParse(request.lockIdentifier) { + fmt.Printf("invalid lock identifier") + w.WriteHeader(http.StatusConflict) + } +} diff --git a/certs/csr.go b/certs/csr.go index 68525aa..0aa3142 100644 --- a/certs/csr.go +++ b/certs/csr.go @@ -5,6 +5,7 @@ import ( "crypto/rand" "crypto/x509" "crypto/x509/pkix" + "encoding/pem" "math/big" "time" @@ -26,7 +27,6 @@ const nanoToSeconds int = 1000000000 const secondsToMinutes int = 60 const secondsToDays int = 86400 - // Sign an Authentication Certificate. May want to do validation of the CSR here. func SignAuthCertificate(csr *x509.CertificateRequest) (*x509.Certificate, error) { return signCSR(csr, AuthCertValidDays) @@ -95,3 +95,30 @@ func signCSR(csr *x509.CertificateRequest, activeDays int) (*x509.Certificate, e return signedCert, nil } + +func DecodeAuthCert(authenticatorCertificate string) ([]byte, error) { + return retrieveAuthCert(authenticatorCertificate, AuthCertValidDays) +} + +// RetrieveAuthCert gets the authenticator certificate, the ca's private key, and the +// number of days that the certificate should be active for and then signs the +// Certificate Signing Request using the root certificate. The function then +// returns a pointer to the resulting x509.Certificate object. +func retrieveAuthCert(authCert string, activeDays int) ([]byte, error) { + + // get the configuration + cfg := util.GetConfig() + + // rootNotBefore := time.Now() + // rootNotAfter := rootNotBefore.Add(time.Duration(nanoToSeconds * secondsToDays * RootCertValidDays)) + + AuthCertBytes := []byte(authCert) + AuthCertPemBlock, _ := pem.Decode(AuthCertBytes) + + decryptedBytes, err := x509.DecryptPEMBlock(AuthCertPemBlock, util.PackPrivateKeyToPemBytes(cfg.PrivateKey)) + if err != nil { + return nil, err + } + + return decryptedBytes, nil +} diff --git a/main.go b/main.go index 83f8675..d741df8 100644 --- a/main.go +++ b/main.go @@ -59,6 +59,10 @@ func main() { router.HandleFunc("/la3/account/create-finish/{username}", api.CreateFinish).Methods("POST") router.HandleFunc("/la3/account/sign-csr/{username}", api.SignCSR).Methods("POST") + router.HandleFunc("/la3/users/{username}/data", api.AuthDataRetrieval).Methods("GET") + router.HandleFunc("/la3/users/{username}/data", api.AuthDataObtainLock).Methods("POST") + router.HandleFunc("/la3/users/{username}/data", api.AuthDataStoreVault).Methods("PUT") + url := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) fmt.Println("Serving Let's Authenticate version 3 API on", url) diff --git a/models/datalocks.go b/models/datalocks.go new file mode 100644 index 0000000..d85bc8f --- /dev/null +++ b/models/datalocks.go @@ -0,0 +1,38 @@ +package models + +import ( + "github.com/google/uuid" + "gorm.io/gorm" +) + +type DataLock struct { + gorm.Model + + UserID uint + LockIdentifier uuid.UUID +} + +func CreateDataLock(d *DataLock) error { + err := db.Create(&d).Error + return err +} + +func CountDataLock(userID uint) (uint, error) { + var count int64 + err := db.Where("user_id = ?", userID).Count(&count).Error + if err != nil { + return 0, err + } + return uint(count), nil +} + +func GetLockByUserID(id uint) (DataLock, error) { + l := DataLock{} + err := db.Where("user_id=?", id).First(&l).Error + + return l, err +} + +func DeleteDataLock(userID uint) error { + return db.Where("user_id = ?", userID).Delete(&DataLock{}).Error +} diff --git a/models/user.go b/models/user.go index d0f7aab..edee321 100644 --- a/models/user.go +++ b/models/user.go @@ -6,16 +6,17 @@ import ( "gorm.io/gorm" - "github.com/duo-labs/webauthn/webauthn" "github.com/duo-labs/webauthn/protocol" + "github.com/duo-labs/webauthn/webauthn" ) // User represents the user model type User struct { gorm.Model - Username string `json:"name" gorm:"not null" validate:"required,min=2,max=25,alphanumunicode"` - DisplayName string `json:"display_name" gorm:"not null"` - Credentials []Credential `json:"credentials"` + Username string `json:"name" gorm:"not null" validate:"required,min=2,max=25,alphanumunicode"` + DisplayName string `json:"display_name" gorm:"not null"` + Credentials []Credential `json:"credentials"` + IsLocked bool `json:"is_locked"` } // NewUser creates and returns a new User @@ -25,6 +26,7 @@ func NewUser(name string) User { user.Username = name user.DisplayName = name + "@letsauth.org" user.Credentials = []Credential{} + user.IsLocked = false return user } @@ -34,7 +36,7 @@ func NewUser(name string) User { func GetUser(id uint) (User, error) { u := User{} err := db.Where("id=?", id).First(&u).Error - + return u, err } @@ -90,8 +92,8 @@ func (u User) WebAuthnCredentials() []webauthn.Credential { for i, cred := range credentials { credentialID, _ := base64.URLEncoding.DecodeString(cred.CredentialID) auth := webauthn.Authenticator{ - AAGUID: cred.Auth.AAGUID, - SignCount: cred.Auth.SignCount, + AAGUID: cred.Auth.AAGUID, + SignCount: cred.Auth.SignCount, CloneWarning: cred.Auth.CloneWarning, } wcs[i] = webauthn.Credential{