|
1 | 1 | package plugin |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "crypto/ed25519" |
| 5 | + "crypto/rand" |
| 6 | + "crypto/tls" |
| 7 | + "crypto/x509" |
| 8 | + "crypto/x509/pkix" |
| 9 | + "encoding/pem" |
| 10 | + "errors" |
4 | 11 | "flag" |
5 | 12 | "os" |
6 | 13 | "slices" |
7 | 14 | "strconv" |
8 | 15 | "strings" |
| 16 | + |
| 17 | + "github.com/gotify/plugin-api/v2/transport" |
9 | 18 | ) |
10 | 19 |
|
11 | | -type PluginCliFlags struct { |
| 20 | +// / PluginCli implements the CLI interface for a Gotify plugin. |
| 21 | +type PluginCli struct { |
12 | 22 | flagSet *flag.FlagSet |
13 | 23 | KexReqFile *os.File |
14 | 24 | KexRespFile *os.File |
15 | 25 | Debug bool |
16 | 26 | } |
17 | 27 |
|
18 | | -func ParsePluginCLIFlags(args []string) (*PluginCliFlags, error) { |
| 28 | +// ParsePluginCli parses the CLI arguments and returns a PluginCli instance. |
| 29 | +func ParsePluginCli(args []string) (*PluginCli, error) { |
19 | 30 | flagSet := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) |
20 | 31 | var kexReqFileName string |
21 | 32 | var kexRespFileName string |
@@ -56,15 +67,68 @@ func ParsePluginCLIFlags(args []string) (*PluginCliFlags, error) { |
56 | 67 | } |
57 | 68 | } |
58 | 69 |
|
59 | | - return &PluginCliFlags{ |
| 70 | + return &PluginCli{ |
60 | 71 | flagSet: flagSet, |
61 | 72 | KexReqFile: kexReqFile, |
62 | 73 | KexRespFile: kexRespFile, |
63 | 74 | Debug: debug, |
64 | 75 | }, nil |
65 | 76 | } |
66 | 77 |
|
67 | | -func (f *PluginCliFlags) Close() error { |
| 78 | +// Kex performs the key exchange through secure file descriptors provided in the arguments. |
| 79 | +func (f *PluginCli) Kex(modulePath string, certPool *x509.CertPool) (certChain []tls.Certificate, err error) { |
| 80 | + // perform key exchange through secure file descriptors |
| 81 | + _, priv, err := ed25519.GenerateKey(rand.Reader) |
| 82 | + if err != nil { |
| 83 | + return nil, err |
| 84 | + } |
| 85 | + csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{ |
| 86 | + Subject: pkix.Name{ |
| 87 | + CommonName: transport.BuildPluginTLSName("*", modulePath), |
| 88 | + }, |
| 89 | + }, priv) |
| 90 | + |
| 91 | + if err != nil { |
| 92 | + return nil, err |
| 93 | + } |
| 94 | + if _, err := f.KexReqFile.Write(pem.EncodeToMemory(&pem.Block{ |
| 95 | + Type: "CERTIFICATE REQUEST", |
| 96 | + Bytes: csrBytes, |
| 97 | + })); err != nil { |
| 98 | + return nil, err |
| 99 | + } |
| 100 | + |
| 101 | + var certificateChain []tls.Certificate |
| 102 | + |
| 103 | + if err := transport.IteratePEMFile(f.KexRespFile, func(block *pem.Block) (continueIterate bool, err error) { |
| 104 | + if block.Type == "CERTIFICATE" { |
| 105 | + parsedCert, err := x509.ParseCertificate(block.Bytes) |
| 106 | + if err != nil { |
| 107 | + return false, err |
| 108 | + } |
| 109 | + certPool.AddCert(parsedCert) |
| 110 | + certificateChain = append(certificateChain, tls.Certificate{ |
| 111 | + Certificate: [][]byte{block.Bytes}, |
| 112 | + Leaf: parsedCert, |
| 113 | + }) |
| 114 | + return true, nil |
| 115 | + } |
| 116 | + return true, nil |
| 117 | + }); err != nil { |
| 118 | + return nil, err |
| 119 | + } |
| 120 | + |
| 121 | + if len(certificateChain) == 0 { |
| 122 | + return nil, errors.New("no certificate chain found in kex response file") |
| 123 | + } |
| 124 | + |
| 125 | + certificateChain[0].PrivateKey = priv |
| 126 | + |
| 127 | + return certificateChain, nil |
| 128 | +} |
| 129 | + |
| 130 | +// Close closes any file descriptors associated with the PluginCli instance. |
| 131 | +func (f *PluginCli) Close() error { |
68 | 132 | if err := f.KexReqFile.Close(); err != nil { |
69 | 133 | return err |
70 | 134 | } |
|
0 commit comments