Skip to content
Open
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
43 changes: 14 additions & 29 deletions api/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,18 @@ import (
"encoding/pem"

"bytes"
"crypto/x509"
"fmt"
"io/ioutil"
"net/http"
"crypto/x509"

"github.com/gorilla/mux"
"github.com/duo-labs/webauthn/protocol"
"github.com/gorilla/mux"

"github.com/Usable-Security-and-Privacy-Lab/lets-auth-ca/models"
"github.com/Usable-Security-and-Privacy-Lab/lets-auth-ca/certs"
"github.com/Usable-Security-and-Privacy-Lab/lets-auth-ca/models"
)

type AuthKeyRequest struct {
AuthPublicKey string `json:"authPublicKey"`
}

type CSRRequest struct {
CSR string `json:"CSR"`
}

type CertificateResponse struct {
Certificate string `json:"certificate"`
}

func CreateBegin(w http.ResponseWriter, r *http.Request) {
fmt.Printf("Started CreateBegin request %s\n", r.RequestURI)
defer fmt.Printf("Finished CreateBegin request %s\n", r.RequestURI)
Expand Down Expand Up @@ -90,7 +78,6 @@ func CreateBegin(w http.ResponseWriter, r *http.Request) {
return
}


jsonResponse(w, options, http.StatusOK)
}

Expand Down Expand Up @@ -154,10 +141,10 @@ func CreateFinish(w http.ResponseWriter, r *http.Request) {
auth := models.MakeAuthenticator(&credential.Authenticator)
credentialID := base64.URLEncoding.EncodeToString(credential.ID)
c := &models.Credential{
Auth: auth,
PublicKey: credential.PublicKey,
CredentialID: credentialID,
UserID: user.ID,
Auth: auth,
PublicKey: credential.PublicKey,
CredentialID: credentialID,
UserID: user.ID,
}
err = models.CreateCredential(c)
if err != nil {
Expand All @@ -172,7 +159,7 @@ func CreateFinish(w http.ResponseWriter, r *http.Request) {
// print it
var bodyBytes []byte
fmt.Println("request:")
bodyBytes, err = ioutil.ReadAll(r.Body)
bodyBytes, err = ioutil.ReadAll(r.Body)
var prettyJSON bytes.Buffer
if err = json.Indent(&prettyJSON, bodyBytes, "", "\t"); err != nil {
fmt.Printf("JSON parse error: %v", err)
Expand All @@ -198,10 +185,9 @@ func CreateFinish(w http.ResponseWriter, r *http.Request) {

fmt.Println("got key", request.AuthPublicKey)


// Store the authenticator public key
authKey := &models.AuthKey{
Key: request.AuthPublicKey,
Key: request.AuthPublicKey,
UserID: user.ID,
}
err = models.CreateAuthKey(authKey)
Expand All @@ -219,22 +205,22 @@ func CreateFinish(w http.ResponseWriter, r *http.Request) {
func SignCSR(w http.ResponseWriter, r *http.Request) {
fmt.Printf("Started SignCSR request %s\n", r.RequestURI)
defer fmt.Printf("Finished SignCSR 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
}

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
}

// Get the CSR from the request
var request CSRRequest
err = json.NewDecoder(r.Body).Decode(&request)
Expand Down Expand Up @@ -270,8 +256,8 @@ func SignCSR(w http.ResponseWriter, r *http.Request) {
// Second, convert the public key in the CSR into PEM format
publicKeyDer, _ := x509.MarshalPKIXPublicKey(csr.PublicKey)
publicKeyBlock := pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyDer,
Type: "PUBLIC KEY",
Bytes: publicKeyDer,
}
publicKey := string(pem.EncodeToMemory(&publicKeyBlock))

Expand Down Expand Up @@ -317,4 +303,3 @@ func jsonResponse(w http.ResponseWriter, d interface{}, c int) {
w.WriteHeader(c)
fmt.Fprintf(w, "%s", dj)
}

24 changes: 17 additions & 7 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,35 @@ package api
import (
"log"

"github.com/duo-labs/webauthn/webauthn"
"github.com/Usable-Security-and-Privacy-Lab/lets-auth-ca/util"
"github.com/duo-labs/webauthn/webauthn"
"github.com/go-playground/validator/v10"
)


var webAuthn *webauthn.WebAuthn
var sessionStore *Store

var validate *validator.Validate

type AuthKeyRequest struct {
AuthPublicKey string `json:"authPublicKey"`
}

type CSRRequest struct {
CSR string `json:"CSR"`
}

type CertificateResponse struct {
Certificate string `json:"certificate"`
}

func Init() {
var err error
cfg := util.GetConfig()
webAuthn, err = webauthn.New(&webauthn.Config{
RPDisplayName: cfg.RPDisplayName, // Display Name for your site
RPID: cfg.RPID, // Generally the domain name for your site
RPOrigin: cfg.RPOrigin, // this needs to be the origin for the request, with the protocol (HTTP(S)) and port number (if not 80 for HTTP or 443 for HTTPS)
RPDisplayName: cfg.RPDisplayName, // Display Name for your site
RPID: cfg.RPID, // Generally the domain name for your site
RPOrigin: cfg.RPOrigin, // this needs to be the origin for the request, with the protocol (HTTP(S)) and port number (if not 80 for HTTP or 443 for HTTPS)
})
if err != nil {
log.Fatal("failed to create WebAuthn from config:", err)
Expand All @@ -31,6 +41,6 @@ func Init() {
if err != nil {
log.Fatal("failed to create session store:", err)
}

validate = validator.New()
}
}
149 changes: 149 additions & 0 deletions api/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package api

import (
"crypto/x509"
"encoding/json"
"encoding/pem"
"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/gorilla/mux"
)

// certificateRequest represents the CSR for a single certificate
type certificateRequest struct {
CSR string `json:"CSR"`
AuthSignature string `json:"authSignature"`
AuthenticatorCertificate string `json:"authenticatorCertificate"`
}

// serviceResponse represents the response made when the CA succesfully
// signs all the certificates
type serviceResponse struct {
SignedCertificate string `json:"serviceCertificate"`
}

func ObtainAccountCertificate(w http.ResponseWriter, r *http.Request) {
fmt.Printf("Beginning serving request %s\n", r.RequestURI)
defer fmt.Printf("Finished serving request %s\n", r.RequestURI)

pathVariables := mux.Vars(r)
username := pathVariables["username"]

reqBody, _ := ioutil.ReadAll(r.Body)
var request certificateRequest
json.Unmarshal(reqBody, &request)

// Verify Authenticator Signature
authCert, CSR, signature := request.AuthenticatorCertificate, request.CSR, request.AuthSignature

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
}

// Sign the Certificate
byteCSR := []byte(CSR)
cert, _ := pem.Decode(byteCSR)
var csr *x509.CertificateRequest

// var err error
if cert == nil {
csr, err = x509.ParseCertificateRequest(byteCSR)
// checkError(err)
} else {
csr, err = x509.ParseCertificateRequest(cert.Bytes)
// checkError(err)
}
if err != nil {
fmt.Println("CSR bad format", err.Error())
jsonResponse(w, err.Error(), http.StatusBadRequest)
return
}

// Check that the CSR is for one of the valid authenticator public keys
// First, get the authorized keys for this user
authKeys, err := models.GetAuthKeysForUser(user)
if err != nil {
jsonResponse(w, "unable to get authenticator keys for this user", http.StatusInternalServerError)
return
}
// Second, convert the public key in the CSR into PEM format
publicKeyDer, _ := x509.MarshalPKIXPublicKey(csr.PublicKey)
publicKeyBlock := pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyDer,
}
publicKey := string(pem.EncodeToMemory(&publicKeyBlock))

// Third, check if the key matches any of the valid keys for this user
present := models.AuthKeyPresent(publicKey, authKeys)
if !present {
jsonResponse(w, "authenticator key is not authorized for this account", http.StatusUnauthorized)
return
}

// TBD we need to validate the CSR. This should include being sure it is properly signed.
// It is for the username this account owns.
// What else?
if username != csr.Subject.CommonName {
jsonResponse(w, "username doesn't match CSR subject", http.StatusBadRequest)
return
}

signedCert, err := certs.SignAuthCertificate(csr)
pemCert := string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: signedCert.Raw}))

if err != nil {
switch {
case pemCert == "Invalid Device Cert":
w.WriteHeader(403)
fmt.Fprint(w, pemCert)
fmt.Println("403, Invalid Device Cert")
// Do these need returns? ask if the old Login endpoint was exhaustively tested
default:
w.WriteHeader(500)
fmt.Fprint(w, pemCert)
fmt.Printf("500, %s\n", pemCert)
}
return
}

err = models.VerifyAccountID(signedCert, username)
if err != nil {
if err.Error() == "account already taken by other user" || err.Error() == "account already exists by current user" {
w.WriteHeader(403)
} else {
w.WriteHeader(500)
}
}

err = certs.VerifyRSASignatureFromCert(signedCert, byteCSR, signature)
// TODO: AuthenticatorCertificate must be signed by CA, and for signed this account
if err != nil {
jsonResponse(w, err.Error(), http.StatusBadRequest)
return
}

/*
// Maybe we don't need this statement
// Add the certificate to the database
err = certs.AddCert(signedCert, authCert)
if err != nil {
// AGAIN ASK FOR CLERIFICATION
jsonResponse(w, fmt.Errorf("failed adding cert to certs table"), http.StatusInternalServerError)
return
}
*/

response := serviceResponse{SignedCertificate: pemCert}
final, _ := json.Marshal(response)

fmt.Fprint(w, string(final))
}
17 changes: 17 additions & 0 deletions api/username_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package api

import (
"testing"

"github.com/stretchr/testify/mock"
)

type mockObtainAccountCertificate struct {
mock.Mock
}

// TODO: implement tests

func TestObtainAccountCertificate(t *testing.T) {

}
Loading