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 } }