diff --git a/pkg/apcssh/shell.go b/pkg/apcssh/shell.go index c6f082c..4643621 100644 --- a/pkg/apcssh/shell.go +++ b/pkg/apcssh/shell.go @@ -79,7 +79,10 @@ func (cli *Client) cmd(command string) (*upsCmdResponse, error) { codeTxtIndx := strings.Index(result, "\n") res.codeText = result[:codeTxtIndx-1] - res.resultText = result[codeTxtIndx+1 : len(result)-2] + // avoid out of bounds if no result text + if codeTxtIndx+1 <= len(result)-2 { + res.resultText = result[codeTxtIndx+1 : len(result)-2] + } break } } diff --git a/pkg/apcssh/ssl.go b/pkg/apcssh/ssl.go index 514545d..c3fac28 100644 --- a/pkg/apcssh/ssl.go +++ b/pkg/apcssh/ssl.go @@ -1,14 +1,74 @@ package apcssh -import "fmt" +import ( + "fmt" + "strings" +) -// InstallSSLCert installs the specified p15 cert file on the UPS. This -// function currently only works on NMC2. -func (cli *Client) InstallSSLCert(keyCertP15 []byte) error { - // install NMC2 P15 file - err := cli.UploadSCP("/ssl/defaultcert.p15", keyCertP15, 0600) +// InstallSSLCert installs the specified p15 key and p15 cert files on the +// UPS. It has logic to deduce if the NMC is a newer version (e.g., NMC3 with +// newer firmware) and acts accordingly. +func (cli *Client) InstallSSLCert(keyP15 []byte, certPem []byte, keyCertP15 []byte) error { + // run `ssl` command to check if it exists + result, err := cli.cmd("ssl") if err != nil { - return fmt.Errorf("apcssh: ssl cert install: failed to send file to ups over scp (%w)", err) + return fmt.Errorf("apcssh: ssl cert install: failed to send ssl cmd (%w)", err) + } + // E101 is the code for "Command Not Found" + supportsSSLCmd := strings.ToLower(result.code) != "e101" + + // if SSL is supported, use that method + if supportsSSLCmd { + return cli.installSSLCertModern(keyP15, certPem) + } + + // fallback to legacy + return cli.installSSLCertLegacy(keyCertP15) +} + +// installSSLCertModern installs the SSL key and certificate using the UPS built-in +// command `ssl`. This command is not present on older devices (e.g., NMC2) or firmwares. +func (cli *Client) installSSLCertModern(keyP15 []byte, certPem []byte) error { + // upload the key P15 file + err := cli.UploadSCP("/ssl/nmc.key", keyP15, 0600) + if err != nil { + return fmt.Errorf("apcssh: ssl cert install: failed to send nmc.key file to ups over scp (%w)", err) + } + + // upload the cert PEM file + err = cli.UploadSCP("/ssl/nmc.crt", certPem, 0666) + if err != nil { + return fmt.Errorf("apcssh: ssl cert install: failed to send nmc.key file to ups over scp (%w)", err) + } + + // run `ssl` install commands + result, err := cli.cmd("ssl key -i /ssl/nmc.key") + if err != nil { + return fmt.Errorf("apcssh: ssl cert install: failed to send ssl key install cmd (%w)", err) + } + if strings.ToLower(result.code) != "e000" { + return fmt.Errorf("apcssh: ssl cert install: ssl key install cmd returned error code (%s: %s)", result.code, result.codeText) + } + + result, err = cli.cmd("ssl cert -i /ssl/nmc.crt") + if err != nil { + return fmt.Errorf("apcssh: ssl cert install: failed to send ssl cert install cmd (%w)", err) + } + if strings.ToLower(result.code) != "e000" { + return fmt.Errorf("apcssh: ssl cert install: ssl cert install cmd returned error code (%s: %s)", result.code, result.codeText) + } + + return nil +} + +// installSSLCertLegacy installs the SSL key and certificate by directly uploading +// them to a .p15 file on the UPS. This is used for older devices (e.g., NMC2) and +// firmwares that do not support the `ssl` command. +func (cli *Client) installSSLCertLegacy(keyCertP15 []byte) error { + // upload/install keyCert P15 file + err := cli.UploadSCP("/ssl/defaultcert.p15", keyCertP15, 0600) + if err != nil { + return fmt.Errorf("apcssh: ssl cert install: failed to send defaultcert.p15 file to ups over scp (%w)", err) } return nil diff --git a/pkg/app/cmd_create.go b/pkg/app/cmd_create.go index c5d700c..de9512d 100644 --- a/pkg/app/cmd_create.go +++ b/pkg/app/cmd_create.go @@ -31,7 +31,7 @@ func (app *app) cmdCreate(_ context.Context, args []string) error { // validation done // make p15 files - apcKeyCertFile, keyFile, err := app.pemToAPCP15s(keyPem, certPem, "create") + keyFile, apcKeyCertFile, err := app.pemToAPCP15(keyPem, certPem, "create") if err != nil { return err } diff --git a/pkg/app/cmd_install.go b/pkg/app/cmd_install.go index f59bd7b..f4b8a22 100644 --- a/pkg/app/cmd_install.go +++ b/pkg/app/cmd_install.go @@ -46,7 +46,7 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error { // validation done // make p15 file - keyCertP15, _, err := app.pemToAPCP15s(keyPem, certPem, "install") + keyP15, keyCertP15, err := app.pemToAPCP15(keyPem, certPem, "install") if err != nil { return err } @@ -66,7 +66,7 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error { } // install SSL Cert - err = client.InstallSSLCert(keyCertP15) + err = client.InstallSSLCert(keyP15, certPem, keyCertP15) if err != nil { return fmt.Errorf("install: failed to send file to ups over scp (%w)", err) } diff --git a/pkg/app/pem_to_p15.go b/pkg/app/pem_to_p15.go index b006c9e..e376bc6 100644 --- a/pkg/app/pem_to_p15.go +++ b/pkg/app/pem_to_p15.go @@ -5,10 +5,10 @@ import ( "fmt" ) -// pemToAPCP15s reads the specified pem files and returns the apc p15 files (both a +// pemToAPCP15 reads the specified pem files and returns the apc p15 files (both a // p15 file with just the private key, and also a p15 file with both the private key // and certificate). The key+cert file includes the required APC header, prepended. -func (app *app) pemToAPCP15s(keyPem, certPem []byte, parentCmdName string) (apcKeyCertFile, keyFile []byte, err error) { +func (app *app) pemToAPCP15(keyPem, certPem []byte, parentCmdName string) (keyFile []byte, apcKeyCertFile []byte, err error) { app.stdLogger.Printf("%s: making apc p15 file from pem", parentCmdName) // make p15 struct @@ -36,5 +36,5 @@ func (app *app) pemToAPCP15s(keyPem, certPem []byte, parentCmdName string) (apcK app.stdLogger.Printf("%s: apc p15 file data succesfully generated", parentCmdName) - return apcKeyCertFile, keyFile, nil + return keyFile, apcKeyCertFile, nil }