This commit is contained in:
jasinco 2025-10-12 20:01:19 +08:00
commit e03def9d03
19 changed files with 1015 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
test
crtman.toml

0
LICENSE Normal file
View file

40
cmd/get.go Normal file
View file

@ -0,0 +1,40 @@
/*
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// getCmd represents the get command
var getCmd = &cobra.Command{
Use: "get",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("get called")
},
}
func init() {
rootCmd.AddCommand(getCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// getCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// getCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
getCmd.Flags().Bool("ca", false, "Get CA certificate")
}

48
cmd/root.go Normal file
View file

@ -0,0 +1,48 @@
/*
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"os"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
var cfgFilePath string
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "crtman",
Short: "A SOHO Certification System",
Long: `This Application is made for TCIVS CSE Competitors' Room.`,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() *pflag.FlagSet {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
return rootCmd.Flags()
}
func init() {
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
rootCmd.PersistentFlags().StringVar(&cfgFilePath, "config", "", "config file (default is $HOME/.crtman.yaml)")
// Cobra also supports local flags, which will only run
// when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
rootCmd.PersistentFlags().StringP("host", "c", "", "Remote/Listen Host IP")
rootCmd.PersistentFlags().StringP("port", "p", "", "Remote/Listen Host Port")
}

89
cmd/serve.go Normal file
View file

@ -0,0 +1,89 @@
/*
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"log"
"math/big"
"github.com/jasinco/crtman/internal/cli"
"github.com/jasinco/crtman/internal/crt"
"github.com/jasinco/crtman/internal/store"
"github.com/jasinco/crtman/internal/web"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var init_prog, reinit bool
// serveCmd represents the serve command
var serveCmd = &cobra.Command{
Use: "serve",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("serve called")
viper.BindPFlag("ca.organization", cmd.Flags().Lookup("ca_org"))
viper.BindPFlag("ca.fqdn", cmd.Flags().Lookup("ca_fqdn"))
viper.BindPFlags(cmd.Flags())
// config handle
err := viper.ReadInConfig()
if err != nil {
log.Printf("can't load config %e\n", err)
}
cli.GetListen()
cli.GetPersistent()
log.Printf("Get Persistent Path: %s", cli.PersistentPath)
store.InitStore(cli.PersistentPath)
defer store.CloseStore()
check_init := store.ChekInit()
if init_prog {
if check_init && !reinit {
log.Fatalln("you have initiazlized the program.")
}
cli.GetCA_CFG()
crt.IssueRoot(cli.CA_config)
store.Serial = *big.NewInt(1)
store.InitStoreData()
check_init = true
}
// Check Root CA
if !check_init {
log.Fatalln("Hasn't Initialized, CA_cert not found")
}
cli.ParseFederation()
web.Listen(cli.Host, cli.Port)
},
}
func init() {
rootCmd.AddCommand(serveCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// serveCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// serveCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
serveCmd.Flags().StringP("store", "s", "", "The place the appstore its persistent stuff")
serveCmd.Flags().BoolVar(&init_prog, "init", false, "Init the settings, create CA, ...etc")
serveCmd.Flags().BoolVar(&reinit, "reinit", false, "It reinit the whole chain(ca, keys)")
serveCmd.Flags().String("ca_org", "", "the CA's organization")
serveCmd.Flags().String("ca_fqdn", "", "the CA's FQDN")
}

22
crtman.template.toml Normal file
View file

@ -0,0 +1,22 @@
store="./test.db`"
[ca]
organization=""
fqdn=""
[federation]
backend="ldap"
auth=["ldap"]
# this ldap config is for LLDAP
[federation.ldap]
auth_user = "uid=admin,ou=people,dc=tcivs,dc=intra"
auth_password= ""
base_url="ldap://localhost:3890"
# user
user_identify = "uid"
user_objectclass = "persons"
#group
group_identify = "cn"
group_objectclass = "groups"
group_base_dn = "ou=group,dc=tcivs,dc=intra"

65
go.mod Normal file
View file

@ -0,0 +1,65 @@
module github.com/jasinco/crtman
go 1.25.1
require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/abcum/lcp v0.0.0-20201209214815-7a3f3840be81 // indirect
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/gin-gonic/gin v1.11.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-ldap/ldap/v3 v3.4.12 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.27.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-immutable-radix/v2 v2.0.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattetti/filebuffer v1.0.1 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.32 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.54.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/cobra v1.10.1 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/spf13/viper v1.21.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
go.mills.io/bitcask/v2 v2.1.3 // indirect
go.uber.org/mock v0.5.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/net v0.45.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/tools v0.37.0 // indirect
google.golang.org/protobuf v1.36.9 // indirect
)

166
go.sum Normal file
View file

@ -0,0 +1,166 @@
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/abcum/lcp v0.0.0-20201209214815-7a3f3840be81 h1:uHogIJ9bXH75ZYrXnVShHIyywFiUZ7OOabwd9Sfd8rw=
github.com/abcum/lcp v0.0.0-20201209214815-7a3f3840be81/go.mod h1:6ZvnjTZX1LNo1oLpfaJK8h+MXqHxcBFBIwkgsv+xlv0=
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap v3.4.12+incompatible h1:rxzUa+YRcrq19g6GYBUHsYVKSVIiY2uvprRFCdR/t5A=
github.com/go-ldap/ldap v3.4.12+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4=
github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hashicorp/go-immutable-radix/v2 v2.0.0 h1:nq9lQ5I71Heg2lRb2/+szuIWKY3Y73d8YKyXyN91WzU=
github.com/hashicorp/go-immutable-radix/v2 v2.0.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM=
github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
go.mills.io/bitcask/v2 v2.1.3 h1:xOe0sdkTHqgpMxsfDjcpaWOm8VtCmA5JzRy4dbicFfk=
go.mills.io/bitcask/v2 v2.1.3/go.mod h1:ZQFykoTTCvMwy24lBstZhSRQuleYIB4EzWKSOgEv6+k=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -0,0 +1,8 @@
package accounting
type Account interface {
IsUser(name string) bool
IsGroup(name string) bool
UserInGroup(user string, group string) bool
AuthUser(name string, password string) bool
}

View file

@ -0,0 +1,117 @@
package accounting
import (
"fmt"
"log"
"github.com/go-ldap/ldap/v3"
)
type LDAP_Config struct {
Base_DN string `mapstructure:"base_dn"`
User_iden string `mapstructure:"user_identify"`
User_objclass string `mapstructure:"user_objectclass"`
Group_iden string `mapstructure:"group_identify"`
Group_base string `mapstructure:"group_base_dn"`
Group_objclass string `mapstructure:"group_objectclass"`
Auth_username string `mapstructure:"auth_user"`
Auth_password string `mapstructure:"auth_password"`
Base_URL string `mapstructure:"base_url"`
}
type LDAP struct {
conn *ldap.Conn
cfg LDAP_Config
}
func (l *LDAP) build_query(filter string) *ldap.SearchRequest {
return ldap.NewSearchRequest(l.cfg.Base_DN,
ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false,
filter,
[]string{"dn"}, nil)
}
func (l *LDAP) IsUser(name string) bool {
req := l.build_query(
fmt.Sprintf("(&(objectClass=%s)(%s=%s))",
l.cfg.User_objclass, l.cfg.User_iden, ldap.EscapeFilter(name),
),
)
sr, err := l.conn.Search(req)
if err != nil {
log.Println(err)
return false
}
if len(sr.Entries) != 1 {
log.Println("Found multiple entries for single user")
return false
}
return true
}
func (l *LDAP) IsGroup(name string) bool {
log.Printf("LDAP: Checking if group %s exists...\n", name)
req := l.build_query(
fmt.Sprintf("(&(objectClass=%s)(%s=%s))",
l.cfg.Group_objclass, l.cfg.Group_iden, ldap.EscapeFilter(name),
),
)
sr, err := l.conn.Search(req)
if err != nil {
log.Println(err)
return false
}
if len(sr.Entries) != 1 {
log.Println("Found multiple entries for single group")
return false
}
return true
}
func (l *LDAP) UserInGroup(user string, group string) bool {
log.Printf("LDAP: Checking if user %s is in group %s...\n", user, group)
req := l.build_query(
fmt.Sprintf("(&(objectClass=%s)(%s=%s)(memberof=%s=%s,%s))",
l.cfg.User_objclass,
l.cfg.User_iden,
ldap.EscapeFilter(user),
l.cfg.Group_iden,
ldap.EscapeFilter(group),
l.cfg.Group_base,
),
)
sr, err := l.conn.Search(req)
if err != nil {
log.Println(err)
return false
}
return len(sr.Entries) != 1
}
func (l *LDAP) AuthUser(name string, password string) bool {
log.Printf("LDAP: Attempting authentication for user %s...\n", name)
return true
}
func NewLDAP(config LDAP_Config) LDAP {
new_dial, err := ldap.DialURL(config.Base_URL)
if err != nil {
log.Fatal(err)
}
err = new_dial.Bind(config.Auth_username, config.Auth_password)
if err != nil {
log.Fatal(err)
}
return LDAP{
conn: new_dial,
cfg: config,
}
}

View file

@ -0,0 +1,49 @@
package cli
import (
"log"
"github.com/jasinco/crtman/internal/accounting"
"github.com/spf13/viper"
)
var backend accounting.Account
var auth_backend map[string]*accounting.Account
func ParseFederation() {
log.Println("Getting Federation")
account_backend := viper.GetString("federation.backend")
log.Printf("Accounting: %s\n", account_backend)
auth := viper.GetStringSlice("federation.auth")
log.Println("Authenticate", auth)
backend = construct(account_backend)
auth_backend = make(map[string]*accounting.Account)
for _, method := range auth {
if method == account_backend {
auth_backend[method] = &backend
} else {
auth := construct(method)
auth_backend[method] = &auth
}
}
}
func construct_ldap() accounting.LDAP {
block_name := "federation.ldap"
var ldap_cfg accounting.LDAP_Config
viper.UnmarshalKey(block_name, &ldap_cfg)
return accounting.NewLDAP(ldap_cfg)
}
func construct(name string) accounting.Account {
var account accounting.Account
switch name {
case "ldap":
ldap_inst := construct_ldap()
account = &ldap_inst
default:
log.Fatal("No Federation")
}
return account
}

44
internal/cli/helper.go Normal file
View file

@ -0,0 +1,44 @@
package cli
import (
"fmt"
"github.com/spf13/viper"
)
type CA_CFG struct {
Org string
FQDN string
}
var Host string
var Port string
var PersistentPath string
var CA_config CA_CFG
// the real endpoint that you should access the server
// e.g. 172.16.0.1:3232
var Outbound string
func GetListen() {
viper.SetDefault("host", "localhost")
viper.SetDefault("port", "8086")
Host = viper.GetString("host")
Port = viper.GetString("port")
viper.SetDefault("outbound", fmt.Sprintf("%s:%s", Host, Port))
Outbound = viper.GetString("outbound")
}
func GetPersistent() {
viper.SetDefault("store", "/tmp/db")
PersistentPath = viper.GetString("store")
}
func GetCA_CFG() {
viper.SetDefault("ca.organization", "TCIVS_CSE_CR")
viper.SetDefault("ca.fqdn", "example.com")
CA_config = CA_CFG{
Org: viper.GetString("ca.organization"),
FQDN: viper.GetString("ca.fqdn"),
}
}

40
internal/crt/cert.go Normal file
View file

@ -0,0 +1,40 @@
package crt
import (
"bytes"
"database/sql"
"github.com/jasinco/crtman/internal/store"
"golang.org/x/crypto/ocsp"
)
func CheckValid(req *ocsp.Request) sql.Null[ocsp.Response] {
var response ocsp.Response
response.Status = ocsp.Revoked
if !req.HashAlgorithm.Available() {
return sql.Null[ocsp.Response]{Valid: false}
}
leaf := store.GetLeafCert(req.SerialNumber)
if !leaf.Valid {
return sql.Null[ocsp.Response]{Valid: false}
}
hasher := req.HashAlgorithm.New()
valid_issuer_dn := bytes.Equal(hasher.Sum(leaf.V.Cert.RawIssuer), req.IssuerKeyHash)
hasher.Reset()
valid_issuer_key := bytes.Equal(hasher.Sum(leaf.V.Cert.RawSubjectPublicKeyInfo), req.IssuerKeyHash)
if !(valid_issuer_dn && valid_issuer_key) {
response.Status = ocsp.Unknown
}
response.SerialNumber = req.SerialNumber
if !leaf.V.RevokeAt.Valid {
response.Status = ocsp.Good
} else {
response.RevokedAt = leaf.V.RevokeAt.Time
response.RevocationReason = int(leaf.V.RevokeReason.Int16)
}
return sql.Null[ocsp.Response]{Valid: true, V: response}
}

92
internal/crt/issue.go Normal file
View file

@ -0,0 +1,92 @@
package crt
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"database/sql"
"fmt"
"log"
"math/big"
"time"
"github.com/jasinco/crtman/internal/cli"
"github.com/jasinco/crtman/internal/store"
)
func IssueRoot(config cli.CA_CFG) {
log.Printf("CA: Org: %s, FQDN: %s\n", config.Org, config.FQDN)
rootca := x509.Certificate{
IsCA: true,
Version: 1,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageOCSPSigning},
Subject: pkix.Name{
Country: []string{"Taiwan"},
Organization: []string{config.Org},
CommonName: config.FQDN,
},
NotBefore: time.Now(),
BasicConstraintsValid: true,
SerialNumber: big.NewInt(0),
}
privkey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatal(err)
}
crt, err := x509.CreateCertificate(rand.Reader, &rootca, &rootca, &privkey.PublicKey, privkey)
if err != nil {
log.Fatal("can't create x509 ca, ", err)
}
// privkey_bytes, err := x509.MarshalECPrivateKey(privkey)
// if err != nil {
// log.Fatal("can't create ecdsa key encoded, ", err)
// }
store.RootCA = crt
store.RootCAKey = privkey
}
// csr is a DER csr
func IssueCert(csr []byte) sql.Null[[]byte] {
req, err := x509.ParseCertificateRequest(csr)
if err != nil {
return sql.Null[[]byte]{Valid: false}
}
if req.CheckSignature() != nil {
return sql.Null[[]byte]{Valid: false}
}
store.SerialLock.Lock()
defer store.SerialLock.Unlock()
cert := x509.Certificate{
Subject: req.Subject,
PublicKey: req.PublicKey,
PublicKeyAlgorithm: req.PublicKeyAlgorithm,
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 720),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
IsCA: false,
BasicConstraintsValid: true,
DNSNames: req.DNSNames,
IPAddresses: req.IPAddresses,
SerialNumber: &store.Serial,
OCSPServer: []string{fmt.Sprintf("https://%s/api/ocsp", cli.Outbound)},
}
ca, err := x509.ParseCertificate(store.RootCA)
if err != nil {
log.Println(err)
return sql.Null[[]byte]{Valid: false}
}
signed, err := x509.CreateCertificate(rand.Reader, &cert, ca, req.PublicKey, store.RootCAKey)
if err != nil {
log.Println(err)
return sql.Null[[]byte]{Valid: false}
}
store.Serial.Add(&store.Serial, big.NewInt(1))
return sql.Null[[]byte]{Valid: true, V: signed}
}

123
internal/store/storage.go Normal file
View file

@ -0,0 +1,123 @@
package store
import (
"context"
"crypto/ecdsa"
"crypto/x509"
"database/sql"
"log"
"math/big"
"sync"
_ "github.com/mattn/go-sqlite3"
)
var DB *sql.DB
var RootCA []byte
var RootCAKey *ecdsa.PrivateKey
var SerialLock sync.Mutex
var Serial big.Int
func InitStore(path string) {
var err error
DB, err = sql.Open("sqlite3", path)
if err != nil {
log.Fatal(err)
}
}
func CloseStore() {
DB.Close()
}
func ChekInit() bool {
result := DB.QueryRow("select id,cert,key,serial from syscfg order by id desc limit 1")
if result.Err() != nil {
log.Println(result.Err())
return false
}
var placeholder int
var temp_serial, temp_ca_key []byte
err := result.Scan(&placeholder, &RootCA, &temp_ca_key, &temp_serial)
if err != nil {
log.Println(err)
return false
}
Serial.SetBytes(temp_serial)
RootCAKey, err = x509.ParseECPrivateKey(temp_ca_key)
if err != nil {
log.Println(err)
return false
}
return true
}
func initSchema(tx *sql.Tx) {
_, err := tx.Exec(`
create table syscfg (id integer not null primary key autoincrement, cert blob not null, key blob not null, serial blob not null);
delete from syscfg;
`)
if err != nil {
log.Fatal(err)
}
_, err = tx.Exec(`
create table leaf (id blob not null primary key, cert blob not null, owner text not null, revokeAt datetime, revokeReason integer);
delete from leaf;
`)
}
func InitStoreData() {
ctx := context.Background()
tx, err := DB.BeginTx(ctx, nil)
if err != nil {
log.Fatal(err)
}
initSchema(tx)
temp_ca_key, err := x509.MarshalECPrivateKey(RootCAKey)
if err != nil {
log.Fatal(err)
}
tx.Exec(`
insert into syscfg(cert,key,serial) values(?,?,?)
`, RootCA, temp_ca_key, Serial.Bytes())
err = tx.Commit()
if err != nil {
log.Fatal(err)
}
}
func InsertCert(cert []byte, applicant string) {
parse, err := x509.ParseCertificate(cert)
if err != nil {
log.Println(err)
return
}
DB.Exec("insert into syscfg(id,cert,owner) values(?,?,?)", parse.SerialNumber.Bytes(), cert, applicant)
}
type LeafCert struct {
Cert_org []byte
Cert *x509.Certificate
RevokeAt sql.NullTime
RevokeReason sql.NullInt16
}
func GetLeafCert(serial_num *big.Int) sql.Null[LeafCert] {
var err error
result := DB.QueryRow("select cert,revokeAt, revokeReason from leaf where id = ?", serial_num.Bytes())
var leaf LeafCert
if result.Err() != nil {
log.Println(result.Err())
return sql.Null[LeafCert]{Valid: false}
}
err = result.Scan(&leaf.Cert_org, &leaf.RevokeAt, &leaf.RevokeReason)
if err != nil {
log.Println(err)
return sql.Null[LeafCert]{Valid: false}
}
leaf.Cert, err = x509.ParseCertificate(leaf.Cert_org)
if err != nil {
log.Println(err)
return sql.Null[LeafCert]{Valid: false}
}
return sql.Null[LeafCert]{V: leaf, Valid: true}
}

56
internal/web/ocsp.go Normal file
View file

@ -0,0 +1,56 @@
package web
import (
"crypto/x509"
"encoding/base64"
"io"
"log"
"github.com/gin-gonic/gin"
"github.com/jasinco/crtman/internal/crt"
"github.com/jasinco/crtman/internal/store"
"golang.org/x/crypto/ocsp"
)
// comply to https://datatracker.ietf.org/doc/html/rfc6960#appendix-A
func ocsp_handling(c *gin.Context) {
var req_bin []byte
var err error
if c.Request.Method == "GET" {
b64_req := c.Param("req")
req_bin, err = base64.RawURLEncoding.DecodeString(b64_req)
if err != nil {
c.Status(400)
return
}
} else {
req_bin, err = io.ReadAll(c.Request.Body)
if err != nil {
c.Status(400)
return
}
}
req, err := ocsp.ParseRequest(req_bin)
if err != nil {
c.Status(400)
return
}
result := crt.CheckValid(req)
if !result.Valid {
c.Status(400)
return
}
ca, err := x509.ParseCertificate(store.RootCA)
if err != nil {
log.Println(err)
c.Status(500)
return
}
response, err := ocsp.CreateResponse(ca, ca, result.V, store.RootCAKey)
if err != nil {
log.Println(err)
c.Status(500)
return
}
c.Data(200, "application/ocsp-response", response)
}

29
internal/web/serve.go Normal file
View file

@ -0,0 +1,29 @@
package web
import (
"encoding/pem"
"fmt"
"log"
"github.com/gin-gonic/gin"
"github.com/jasinco/crtman/internal/store"
)
func Listen(host string, port string) {
r := gin.Default()
r.GET("/api/rootca.pem", func(ctx *gin.Context) {
ca_pem := pem.Block{
Type: "CERTIFICATE",
Bytes: store.RootCA,
}
ctx.Data(200, "application/x-x509-ca-cert", pem.EncodeToMemory(&ca_pem))
})
r.GET("/api/ocsp/:req", ocsp_handling)
r.POST("/api/ocsp", ocsp_handling)
err := r.Run(fmt.Sprintf("%s:%s", host, port))
if err != nil {
log.Fatal(err)
}
}

4
justfile Normal file
View file

@ -0,0 +1,4 @@
run:
CGO_ENABLED=1 go run ./main.go serve
serveinit:
CGO_ENABLED=1 go run ./main.go serve --init

21
main.go Normal file
View file

@ -0,0 +1,21 @@
/*
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
*/
package main
import (
"github.com/jasinco/crtman/cmd"
"github.com/spf13/viper"
)
func init() {
viper.SetConfigName("crtman.toml")
viper.AddConfigPath("/etc/")
viper.AddConfigPath("$HOME/")
viper.AddConfigPath(".")
viper.SetConfigType("toml")
}
func main() {
cmd.Execute()
}