package api import ( "crypto/rand" "encoding/json" "errors" "fmt" "io/ioutil" "log" "math/big" "os" "path" "time" "strconv" "srs.epita.fr/fic-server/admin/pki" "srs.epita.fr/fic-server/libfic" "github.com/julienschmidt/httprouter" ) var TeamsDir string 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(2019, 01, 19, 0, 0, 0, 0, time.UTC), time.Date(2019, 01, 23, 23, 59, 59, 0, time.UTC)) })) router.GET("/api/teams/:tid/certificates", apiHandler(teamHandler( func(team fic.Team, _ []byte) (interface{}, error) { if serials, err := pki.GetTeamSerials(TeamsDir, team.Id); err != nil { return nil, err } else { var certs []fic.Certificate for _, serial := range serials { if cert, err := fic.GetCertificate(serial); err == nil { certs = append(certs, cert) } else { log.Println("Unable to get back certificate, whereas an association exists on disk: ", err) } } return certs, nil } }))) 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 var serial_gen [8]byte if _, err := rand.Read(serial_gen[:]); err != nil { return nil, err } for fic.ExistingCertSerial(serial_gen) { if _, err := rand.Read(serial_gen[:]); err != nil { return nil, err } } var serial_b big.Int serial_b.SetBytes(serial_gen[:]) serial := serial_b.Uint64() // 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 *uint64 `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 _, cert := range certificates { dstLinkPath := path.Join(TeamsDir, pki.GetCertificateAssociation(cert.Id)) var idTeam *uint64 = nil if lnk, err := os.Readlink(dstLinkPath); err == nil { if tid, err := strconv.ParseUint(lnk, 10, 64); err == nil { idTeam = &tid } } ret = append(ret, CertExported{fmt.Sprintf("%d", cert.Id), cert.Creation, idTeam, cert.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 } dstLinkPath := path.Join(TeamsDir, pki.GetCertificateAssociation(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) } return cert, nil }