273 lines
7.2 KiB
Go
273 lines
7.2 KiB
Go
package api
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/base32"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"math/big"
|
|
"os"
|
|
"path"
|
|
"time"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"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/htpasswd", apiHandler(
|
|
func(httprouter.Params, []byte) (interface{}, error) {
|
|
return genHtpasswd()
|
|
}))
|
|
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/teams/:tid/associations", apiHandler(teamHandler(
|
|
func(team fic.Team, _ []byte) (interface{}, error) {
|
|
return pki.GetTeamAssociations(TeamsDir, team.Id)
|
|
})))
|
|
router.POST("/api/teams/:tid/associations/:assoc", apiHandler(teamAssocHandler(
|
|
func(team fic.Team, assoc string, _ []byte) (interface{}, error) {
|
|
if err := os.Symlink(fmt.Sprintf("%d", team.Id), path.Join(TeamsDir, assoc)); err != nil {
|
|
return nil, err
|
|
}
|
|
return "\"" + assoc + "\"", nil
|
|
})))
|
|
router.DELETE("/api/teams/:tid/associations/:assoc", apiHandler(teamAssocHandler(
|
|
func(team fic.Team, assoc string, _ []byte) (interface{}, error) {
|
|
if err := os.Remove(path.Join(TeamsDir, assoc)); err != nil {
|
|
return nil, err
|
|
}
|
|
return "null", 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 genHtpasswd() (ret string, err error) {
|
|
var teams []fic.Team
|
|
teams, err = fic.GetTeams()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for _, team := range teams {
|
|
var serials []uint64
|
|
serials, err = pki.GetTeamSerials(TeamsDir, team.Id)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if len(serials) == 0 {
|
|
// Don't include teams that don't have associated certificates
|
|
continue
|
|
}
|
|
|
|
for _, serial := range serials {
|
|
var cert fic.Certificate
|
|
cert, err = fic.GetCertificate(serial)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if cert.Revoked != nil {
|
|
continue
|
|
}
|
|
|
|
b := make([]byte, 5)
|
|
if _, err = rand.Read(b); err != nil {
|
|
return
|
|
}
|
|
salt := base32.StdEncoding.EncodeToString(b)
|
|
|
|
ret += fmt.Sprintf("%s:$apr1$%s$%s\n", strings.ToLower(team.Name), salt, fic.Apr1Md5(cert.Password, salt))
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Mark team as active to ensure it'll be generated
|
|
if ut, err := fic.GetTeam(*uc.Team); err != nil {
|
|
return nil, err
|
|
} else if !ut.Active {
|
|
ut.Active = true
|
|
ut.Update()
|
|
}
|
|
} else {
|
|
os.Remove(dstLinkPath)
|
|
}
|
|
|
|
return cert, nil
|
|
}
|