From 0259ae8f94522e90bce85afbc788340eb2ec3f53 Mon Sep 17 00:00:00 2001 From: nemunaire Date: Sun, 21 Jan 2018 14:18:26 +0100 Subject: [PATCH] Introducing new PKI management --- admin/api/certificate.go | 160 ++++++++++++++++++++++++++++++ admin/api/claim.go | 2 +- admin/api/handlers.go | 16 ++- admin/index.go | 1 + admin/main.go | 13 ++- admin/pki/ca.go | 133 +++++++++++++++++++++++++ admin/pki/client.go | 81 +++++++++++++++ admin/pki/common.go | 76 ++++++++++++++ admin/static.go | 3 + admin/static/index.html | 1 + admin/static/js/app.js | 108 ++++++++++++++------ admin/static/views/pki.html | 126 +++++++++++++++++++++++ admin/static/views/team-edit.html | 29 ++++-- backend/main.go | 28 +++++- backend/registration.go | 16 ++- libfic/certificate.go | 101 +++++++++++++++++++ libfic/db.go | 12 +++ libfic/team.go | 2 +- libfic/todo.go | 2 +- 19 files changed, 857 insertions(+), 53 deletions(-) create mode 100644 admin/pki/ca.go create mode 100644 admin/pki/client.go create mode 100644 admin/pki/common.go create mode 100644 admin/static/views/pki.html diff --git a/admin/api/certificate.go b/admin/api/certificate.go index f4caee9e..69e762e2 100644 --- a/admin/api/certificate.go +++ b/admin/api/certificate.go @@ -1,7 +1,167 @@ package api import ( + "encoding/json" + "errors" + "fmt" + "math/rand" + "io/ioutil" + "os" + "path" + "time" + + "srs.epita.fr/fic-server/admin/pki" + "srs.epita.fr/fic-server/libfic" + + "github.com/julienschmidt/httprouter" ) func init() { + router.GET("/api/ca/", apiHandler(infoCA)) + router.GET("/api/ca.pem", apiHandler(getCAPEM)) + router.POST("/api/ca/new", apiHandler( + func(_ httprouter.Params, _ []byte) (interface{}, error) { return true, pki.GenerateCA(time.Date(2018, 01, 21, 0, 0, 0, 0, time.UTC), time.Date(2018, 01, 24, 23, 59, 59, 0, time.UTC)) })) + + router.GET("/api/teams/:tid/certificates", apiHandler(teamHandler( + func(team fic.Team, _ []byte) (interface{}, error) { return fic.GetTeamCertificates(team) }))) + + router.GET("/api/certs/", apiHandler(getCertificates)) + router.POST("/api/certs/", apiHandler(generateClientCert)) + router.DELETE("/api/certs/", apiHandler(func(_ httprouter.Params, _ []byte) (interface{}, error) { return fic.ClearCertificates() })) + + router.HEAD("/api/certs/:certid", apiHandler(certificateHandler(getTeamP12File))) + router.GET("/api/certs/:certid", apiHandler(certificateHandler(getTeamP12File))) + router.PUT("/api/certs/:certid", apiHandler(certificateHandler(updateCertificateAssociation))) + router.DELETE("/api/certs/:certid", apiHandler(certificateHandler( + func(cert fic.Certificate, _ []byte) (interface{}, error) { return cert.Revoke() }))) +} + +func infoCA(_ httprouter.Params, _ []byte) (interface{}, error) { + _, cacert, err := pki.LoadCA() + if err != nil { + return nil, err + } + + ret := map[string]interface{}{} + + ret["version"] = cacert.Version + ret["serialnumber"] = cacert.SerialNumber + ret["issuer"] = cacert.Issuer + ret["subject"] = cacert.Subject + ret["notbefore"] = cacert.NotBefore + ret["notafter"] = cacert.NotAfter + ret["signatureAlgorithm"] = cacert.SignatureAlgorithm + ret["publicKeyAlgorithm"] = cacert.PublicKeyAlgorithm + + return ret, nil +} + +func getCAPEM(_ httprouter.Params, _ []byte) (interface{}, error) { + if _, err := os.Stat(pki.CACertPath()); os.IsNotExist(err) { + return nil, errors.New("Unable to locate the CA root certificate. Have you generated it?") + } else if fd, err := os.Open(pki.CACertPath()); err != nil { + return nil, err + } else { + defer fd.Close() + return ioutil.ReadAll(fd) + } +} + +func getTeamP12File(cert fic.Certificate, _ []byte) (interface{}, error) { + // Create p12 if necessary + if _, err := os.Stat(pki.ClientP12Path(cert.Id)); os.IsNotExist(err) { + if err := pki.WriteP12(cert.Id, cert.Password); err != nil { + return nil, err + } + } + + if _, err := os.Stat(pki.ClientP12Path(cert.Id)); os.IsNotExist(err) { + return nil, errors.New("Unable to locate the p12. Have you generated it?") + } else if fd, err := os.Open(pki.ClientP12Path(cert.Id)); err != nil { + return nil, err + } else { + defer fd.Close() + return ioutil.ReadAll(fd) + } +} + +func generateClientCert(_ httprouter.Params, _ []byte) (interface{}, error) { + // First, generate a new, unique, serial + serial := rand.Int63() + for fic.ExistingCertSerial(serial) { + serial = rand.Int63() + } + + // Let's pick a random password + password, err := pki.GeneratePassword() + if err != nil { + return nil, err + } + + // Ok, now load CA + capriv, cacert, err := pki.LoadCA() + if err != nil { + return nil, err + } + + // Generate our privkey + if err := pki.GenerateClient(serial, cacert.NotBefore, cacert.NotAfter, &cacert, &capriv); err != nil { + return nil, err + } + + // Save in DB + return fic.RegisterCertificate(serial, password) +} + +type CertExported struct { + Id string `json:"id"` + Creation time.Time `json:"creation"` + IdTeam *int64 `json:"id_team"` + Revoked *time.Time `json:"revoked"` +} + +func getCertificates(_ httprouter.Params, _ []byte) (interface{}, error) { + if certificates, err := fic.GetCertificates(); err != nil { + return nil, err + } else { + ret := make([]CertExported, 0) + for _, c := range certificates { + ret = append(ret, CertExported{fmt.Sprintf("%d", c.Id), c.Creation, c.IdTeam, c.Revoked}) + } + return ret, nil + } +} + + +type CertUploaded struct { + Team *int64 `json:"id_team"` +} + +func updateCertificateAssociation(cert fic.Certificate, body []byte) (interface{}, error) { + var uc CertUploaded + if err := json.Unmarshal(body, &uc); err != nil { + return nil, err + } + + // TODO: This should be read from file system, not in DB: + // the relation is made through a symlink, so if it exists, it is suffisant to read the relation + // moreover, backend doesn't update the DB at registration, it only creates a symlink + cert.IdTeam = uc.Team + + dstLinkPath := path.Join(TeamsDir, fmt.Sprintf("_AUTH_ID_%X", cert.Id)) + + if uc.Team != nil { + srcLinkPath := fmt.Sprintf("%d", *uc.Team) + if err := os.Symlink(srcLinkPath, dstLinkPath); err != nil { + return nil, err + } + } else { + os.Remove(dstLinkPath) + } + + if _, err := cert.Update(); err != nil { + return nil, err + } else { + return cert, err + } } diff --git a/admin/api/claim.go b/admin/api/claim.go index a5e815fc..4145a2c0 100644 --- a/admin/api/claim.go +++ b/admin/api/claim.go @@ -79,7 +79,7 @@ func showClaim(claim fic.Claim, _ []byte) (interface{}, error) { type ClaimUploaded struct { Subject string `json:"subject"` - Team *int `json:"id_team"` + Team *int64 `json:"id_team"` Assignee *int64 `json:"id_assignee"` Priority string `json:"priority"` } diff --git a/admin/api/handlers.go b/admin/api/handlers.go index b61d2d50..8824b567 100644 --- a/admin/api/handlers.go +++ b/admin/api/handlers.go @@ -84,7 +84,7 @@ func apiHandler(f DispatchFunction) func(http.ResponseWriter, *http.Request, htt func teamPublicHandler(f func(*fic.Team,[]byte) (interface{}, error)) func (httprouter.Params,[]byte) (interface{}, error) { return func (ps httprouter.Params, body []byte) (interface{}, error) { - if tid, err := strconv.Atoi(string(ps.ByName("tid"))); err != nil { + if tid, err := strconv.ParseInt(string(ps.ByName("tid")), 10, 64); err != nil { return nil, err } else if tid == 0 { return f(nil, body) @@ -98,7 +98,7 @@ func teamPublicHandler(f func(*fic.Team,[]byte) (interface{}, error)) func (http func teamHandler(f func(fic.Team,[]byte) (interface{}, error)) func (httprouter.Params,[]byte) (interface{}, error) { return func (ps httprouter.Params, body []byte) (interface{}, error) { - if tid, err := strconv.Atoi(string(ps.ByName("tid"))); err != nil { + if tid, err := strconv.ParseInt(string(ps.ByName("tid")), 10, 64); err != nil { return nil, err } else if team, err := fic.GetTeam(tid); err != nil { return nil, err @@ -280,6 +280,18 @@ func fileHandler(f func(fic.EFile,[]byte) (interface{}, error)) func (httprouter } } +func certificateHandler(f func(fic.Certificate,[]byte) (interface{}, error)) func (httprouter.Params,[]byte) (interface{}, error) { + return func (ps httprouter.Params, body []byte) (interface{}, error) { + if certid, err := strconv.ParseInt(string(ps.ByName("certid")), 10, 64); err != nil { + return nil, err + } else if cert, err := fic.GetCertificate(certid); err != nil { + return nil, err + } else { + return f(cert, body) + } + } +} + func notFound(ps httprouter.Params, _ []byte) (interface{}, error) { return nil, nil } diff --git a/admin/index.go b/admin/index.go index 939b9250..989831c6 100644 --- a/admin/index.go +++ b/admin/index.go @@ -31,6 +31,7 @@ const indextpl = `