Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions api/authdata.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
29 changes: 28 additions & 1 deletion certs/csr.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"time"

Expand All @@ -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)
Expand Down Expand Up @@ -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
}
4 changes: 4 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
38 changes: 38 additions & 0 deletions models/datalocks.go
Original file line number Diff line number Diff line change
@@ -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
}
16 changes: 9 additions & 7 deletions models/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Expand All @@ -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
}

Expand Down Expand Up @@ -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{
Expand Down