diff --git a/.github/workflows/build_releases.yml b/.github/workflows/build_releases.yml index 5b41dbe..d1a0d90 100644 --- a/.github/workflows/build_releases.yml +++ b/.github/workflows/build_releases.yml @@ -8,11 +8,11 @@ on: env: GITHUB_REF: ${{ github.ref }} - GO_VERSION: '1.24.2' + GO_VERSION: '1.22.1' jobs: build-common: - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest steps: - name: Checkout Main Repo @@ -40,10 +40,8 @@ jobs: name: CHANGELOG.md path: ./CHANGELOG.md -### - build-linux-arm64: - runs-on: ubuntu-24.04-arm + runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@v4 @@ -52,6 +50,12 @@ jobs: ref: ${{ env.GITHUB_REF }} fetch-depth: 0 + - name: Update apt + run: sudo apt update + + - name: Install cross-compiler for linux/arm64 + run: sudo apt-get -y install gcc-aarch64-linux-gnu + - name: Set up Go uses: actions/setup-go@v5 with: @@ -62,6 +66,7 @@ jobs: env: GOOS: linux GOARCH: arm64 + CC: aarch64-linux-gnu-gcc CGO_ENABLED: 0 - name: Save Compiled Binary @@ -85,7 +90,7 @@ jobs: path: ./apc-p15-install build-linux-amd64: - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest steps: - name: Checkout Backend Repo uses: actions/checkout@v4 @@ -166,93 +171,9 @@ jobs: name: apc-p15-install-windows-amd64 path: ./apc-p15-install.exe - build-darwin-arm64: - runs-on: macos-15 - steps: - - name: Checkout Backend Repo - uses: actions/checkout@v4 - with: - repository: gregtwallace/apc-p15-tool - ref: ${{ env.GITHUB_REF }} - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '${{ env.GO_VERSION }}' - - - name: Build Tool - run: go build -o ./apc-p15-tool -v ./cmd/tool - env: - GOOS: darwin - GOARCH: arm64 - CGO_ENABLED: 0 - - - name: Save Compiled Binary - uses: actions/upload-artifact@v4 - with: - name: apc-p15-tool-darwin-arm64 - path: ./apc-p15-tool - - - name: Build Install Only - run: go build -o ./apc-p15-install -v ./cmd/install_only - env: - GOOS: darwin - GOARCH: arm64 - CGO_ENABLED: 0 - - - name: Save Compiled Binary - uses: actions/upload-artifact@v4 - with: - name: apc-p15-install-darwin-arm64 - path: ./apc-p15-install - - build-darwin-amd64: - runs-on: macos-13 - steps: - - name: Checkout Backend Repo - uses: actions/checkout@v4 - with: - repository: gregtwallace/apc-p15-tool - ref: ${{ env.GITHUB_REF }} - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '${{ env.GO_VERSION }}' - - - name: Build Tool - run: go build -o ./apc-p15-tool -v ./cmd/tool - env: - GOOS: darwin - GOARCH: amd64 - CGO_ENABLED: 0 - - - name: Save Compiled Binary - uses: actions/upload-artifact@v4 - with: - name: apc-p15-tool-darwin-amd64 - path: ./apc-p15-tool - - - name: Build Install Only - run: go build -o ./apc-p15-install -v ./cmd/install_only - env: - GOOS: darwin - GOARCH: amd64 - CGO_ENABLED: 0 - - - name: Save Compiled Binary - uses: actions/upload-artifact@v4 - with: - name: apc-p15-install-darwin-amd64 - path: ./apc-p15-install - -### - release-file-linux-arm64: needs: [build-common, build-linux-arm64] - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest steps: - name: Make release directory @@ -296,7 +217,7 @@ jobs: release-file-linux-amd64: needs: [build-common, build-linux-amd64] - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest steps: - name: Make release directory @@ -340,7 +261,7 @@ jobs: release-file-windows-amd64: needs: [build-common, build-windows-amd64] - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest steps: - name: Make release directory @@ -381,91 +302,3 @@ jobs: with: name: apc-p15-tool_windows_amd64 path: ./release - - release-file-darwin-arm64: - needs: [build-common, build-darwin-arm64] - runs-on: ubuntu-24.04 - - steps: - - name: Make release directory - run: mkdir ./release - - - name: Download Tool Binary - uses: actions/download-artifact@v4 - with: - name: apc-p15-tool-darwin-arm64 - path: ./release - - - name: Download Install Binary - uses: actions/download-artifact@v4 - with: - name: apc-p15-install-darwin-arm64 - path: ./release - - - name: Download README - uses: actions/download-artifact@v4 - with: - name: README.md - path: ./release - - - name: Download LICENSE - uses: actions/download-artifact@v4 - with: - name: LICENSE.md - path: ./release - - - name: Download CHANGELOG - uses: actions/download-artifact@v4 - with: - name: CHANGELOG.md - path: ./release - - - name: Save Release - uses: actions/upload-artifact@v4 - with: - name: apc-p15-tool_darwin_arm64 - path: ./release - - release-file-darwin-amd64: - needs: [build-common, build-darwin-amd64] - runs-on: ubuntu-24.04 - - steps: - - name: Make release directory - run: mkdir ./release - - - name: Download Tool Binary - uses: actions/download-artifact@v4 - with: - name: apc-p15-tool-darwin-amd64 - path: ./release - - - name: Download Install Binary - uses: actions/download-artifact@v4 - with: - name: apc-p15-install-darwin-amd64 - path: ./release - - - name: Download README - uses: actions/download-artifact@v4 - with: - name: README.md - path: ./release - - - name: Download LICENSE - uses: actions/download-artifact@v4 - with: - name: LICENSE.md - path: ./release - - - name: Download CHANGELOG - uses: actions/download-artifact@v4 - with: - name: CHANGELOG.md - path: ./release - - - name: Save Release - uses: actions/upload-artifact@v4 - with: - name: apc-p15-tool_darwin_amd64 - path: ./release diff --git a/CHANGELOG.md b/CHANGELOG.md index cc534a6..39c9104 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,60 +1,5 @@ # APC P15 Tool Changelog -## [v1.2.2] - 2025-04-22 - -All dependencies updated. - -Add darwin arm64 and amd64 builds. - - -## [v1.2.1] - 2025-03-17 - -Fix time check for UPS when it is set to GMT timezone. - -All dependencies updated. - - -## [v1.2.0] - 2025-01-27 - -Add a new feature to `install` that checks the time of the UPS to confirm -it is accurate. A log message is added that advises either way. Even if -the check fails, the install still proceeds with attempting to install -the new certificate. - -Dependencies were also all updated. - - -## [v1.1.0] - 2024-09-17 - -> [!IMPORTANT] -> The flag `apchost` on the `install` command has been renamed to -> `hostname`. This flag should contain the hostname only. If a non- -> default SSH port is needed, specify it in the `sshport` flag. - -This version brings support for for RSA 4,092 bit and EC keys. These -keys are only compatible with NMC3 running newer firmwares. To know -if your firmware is new enough, SSH into your UPS and type `ssh` and enter. -If the UPS responds `Command Not Found` the firmware is too old or -otherwise incompatible. - -This version also adds a post `install` check that connects to the web -ui and verifies the certificate served is the expected one. You can -specify a non standard ssl port with the `sslport` flag or skip the check -entirely with the `skipverify` flag. - - -## [v1.0.0] - 2024-07-01 - -First official stable release. - -Fixes Go version in Github action. - - -## [v0.5.3] - 2024-06-24 - -Add 3,072 bit RSA key support. - - ## [v0.5.2] - 2024-06-19 Minor tweak to the previous version. Add timeout for shell diff --git a/README.md b/README.md index 8a9218f..b41e8f3 100644 --- a/README.md +++ b/README.md @@ -58,42 +58,22 @@ and licensed under the GPL-3.0 license. Both NMC2 and NMC3 devices should be fully supported. However, I have one NMC2 device in a home lab and have no way to guarantee success in all cases. -### Key Types and Sizes +Only RSA 1,024 and 2,048 bit keys are accepted. 1,024 bit RSA is no longer +considered completely secure; avoid keys of this size if possible. Most +(all?) public ACME services won't accept keys of this size anyway. -NMC2: -- RSA 1,024, 2,048, 3,072* bit lengths. - -NMC3*: -- RSA 1,024, 2,048, 3,072, and 4,092 bit lengths. -- ECDSA curves P-256, P-384, and P-521. - -\* 3,072 bit length is not officially supported by my NMC2, but appears to work - fine. - -\* The additional key types supported by NMC3 require newer firmware on the - device. I am unsure what the version cutoff is, but you can check support - by connecting to the UPS via SSH and typing `ssl`. If `Command Not Found` - is returned, the firmware is too old and only the key types listed under - NMC2 will work. - -1,024 bit RSA is no longer considered completely secure; avoid keys of -this size if possible. Most (all?) public ACME services won't accept keys -of this size anyway. - -### General Troubleshooting +Even though later versions of the NMC3 firmware supports RSA 4,096 and +ECDSA keys, this tool does not. These options were not available in APC's +proprietary tool, and as such I have no way to generate files to reverse +engineer. My setup (and therefore the testing setup) is: - APC Smart-UPS 1500VA RM 2U SUA1500RM2U (Firmware Revision 667.18.D) -- AP9631 NMC2 Hardware Revision 05 running AOS v7.1.2 and Boot Monitor +- AP9631 NMC2 Hardware Revision 05 running AOS v7.0.4 and Boot Monitor v1.0.9. -If you have trouble, your first step should be to update your NMC's firmware. -Many issues with this tool will be resolved simply by updating to the newest -firmware. - -If you have a problem after that, please post the log in an issue and I can -try to fix it but it may be difficult without your particular hardware to -test with. +If you have problems, please post the log in an issue and I can try to fix it +but it may be difficult without your particular hardware to test with. In particular, if you are experiencing `ssh: handshake failed:` first try using the `--insecurecipher` flag. If this works, you should upgrade your @@ -146,7 +126,7 @@ disk. It instead installs the files directly on the NMC. Logic automatically deduces if the device is an NMC2 or NMC3 and performs the appropriate installation steps. -e.g. `./apc-p15-tool install --keyfile ./apckey.pem --certfile ./apccert.pem --hostname myapc.example.com --username apc --password someSecret --fingerprint 123abc` +e.g. `./apc-p15-tool install --keyfile ./apckey.pem --certfile ./apccert.pem --apchost myapc.example.com:22 --username apc --password someSecret --fingerprint 123abc` ## Note About Install Automation @@ -171,11 +151,6 @@ separate script.  -## Links - -@Owl-Tec's write up using this tool with ACDS: -https://owltec.ca/Windows+Server/Deploying+An+Internal+HTTPS+Certificate+for+a+UPS+APC+with+ADCS+(Active+Directory+Certificate+Services)+with+APC+P15+Tool - ## Thanks Special thanks to the following people and resources which helped me diff --git a/build.ps1 b/build.ps1 index 0c8774b..7147a33 100644 --- a/build.ps1 +++ b/build.ps1 @@ -34,25 +34,3 @@ $env:GOARCH = "arm64" $env:GOOS = "linux" $env:CGO_ENABLED = 0 go build -o $outDir/apc-p15-install-arm64 ./cmd/install_only - -# Darwin (MacOS) amd64 -$env:GOARCH = "amd64" -$env:GOOS = "darwin" -$env:CGO_ENABLED = 0 -go build -o $outDir/apc-p15-tool-darwin-amd64 ./cmd/tool - -$env:GOARCH = "amd64" -$env:GOOS = "darwin" -$env:CGO_ENABLED = 0 -go build -o $outDir/apc-p15-install-darwin-amd64 ./cmd/install_only - -# Darwin (MacOS) arm64 -$env:GOARCH = "arm64" -$env:GOOS = "darwin" -$env:CGO_ENABLED = 0 -go build -o $outDir/apc-p15-tool-darwin-arm64 ./cmd/tool - -$env:GOARCH = "arm64" -$env:GOOS = "darwin" -$env:CGO_ENABLED = 0 -go build -o $outDir/apc-p15-install-darwin-arm64 ./cmd/install_only diff --git a/go.mod b/go.mod index 76eb3ca..ad1bc05 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,14 @@ module apc-p15-tool -go 1.24.2 +go 1.22.4 require ( github.com/peterbourgon/ff/v4 v4.0.0-alpha.4 - github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 - golang.org/x/crypto v0.37.0 + github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 + golang.org/x/crypto v0.18.0 ) -require golang.org/x/sys v0.32.0 // indirect +require golang.org/x/sys v0.16.0 // indirect replace apc-p15-tool/cmd/install_only => /cmd/install_only diff --git a/go.sum b/go.sum index 0b94b0b..96676f0 100644 --- a/go.sum +++ b/go.sum @@ -2,13 +2,13 @@ github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/peterbourgon/ff/v4 v4.0.0-alpha.4 h1:aiqS8aBlF9PsAKeMddMSfbwp3smONCn3UO8QfUg0Z7Y= github.com/peterbourgon/ff/v4 v4.0.0-alpha.4/go.mod h1:H/13DK46DKXy7EaIxPhk2Y0EC8aubKm35nBjBe8AAGc= -github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 h1:NVK+OqnavpyFmUiKfUMHrpvbCi2VFoWTrcpI7aDaJ2I= -github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= -golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs= +github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/img/apc-p15-tool.png b/img/apc-p15-tool.png index c537585..807fb84 100644 Binary files a/img/apc-p15-tool.png and b/img/apc-p15-tool.png differ diff --git a/pkg/apcssh/cmd_gettime.go b/pkg/apcssh/cmd_gettime.go deleted file mode 100644 index 139b0ba..0000000 --- a/pkg/apcssh/cmd_gettime.go +++ /dev/null @@ -1,62 +0,0 @@ -package apcssh - -import ( - "fmt" - "regexp" - "strings" - "time" -) - -// GetTime sends the APC `system` command and then attempts to parse the -// response to determine the UPS current date/time. -func (cli *Client) GetTime() (time.Time, error) { - result, err := cli.cmd("date") - if err != nil { - return time.Time{}, fmt.Errorf("apcssh: failed to get time (%s)", err) - } else if !strings.EqualFold(result.code, "e000") { - return time.Time{}, fmt.Errorf("apcssh: failed to get time (%s: %s)", result.code, result.codeText) - } - - // capture each portion of the date information - regex := regexp.MustCompile(`Date:\s*(\S*)\s*[\r\n]Time:\s*(\S*)\s*[\r\n]Format:\s*(\S*)\s*[\r\n]Time Zone:\s*(\S*)\s*[\r\n]?`) - datePieces := regex.FindStringSubmatch(result.resultText) - if len(datePieces) != 5 { - return time.Time{}, fmt.Errorf("apcssh: failed to get time (length of datetime value pieces was %d (expected: 5))", len(datePieces)) - } - dateVal := datePieces[1] - timeVal := datePieces[2] - formatUPSVal := datePieces[3] - timeZoneVal := datePieces[4] - - // GMT time requires + prefix - // APC UPS fails to use the required +, so add it - if timeZoneVal == "00:00" { - timeZoneVal = "+" + timeZoneVal - } - - // known APC UPS format strings - dateFormatVal := "" - switch formatUPSVal { - case "mm/dd/yyyy": - dateFormatVal = "01/02/2006" - case "dd.mm.yyyy": - dateFormatVal = "02.01.2006" - case "mmm-dd-yy": - dateFormatVal = "Jan-02-06" - case "dd-mmm-yy": - dateFormatVal = "02-Jan-06" - case "yyyy-mm-dd": - dateFormatVal = "2006-01-02" - - default: - return time.Time{}, fmt.Errorf("apcssh: failed to get time (ups returned unknown format string (%s)", formatUPSVal) - } - - // convert to time.Time - t, err := time.Parse(dateFormatVal+" 15:04:05 -07:00", dateVal+" "+timeVal+" "+timeZoneVal) - if err != nil { - return time.Time{}, fmt.Errorf("apcssh: failed to get time (time parse failed: %s)", err) - } - - return t, nil -} diff --git a/pkg/apcssh/ssl.go b/pkg/apcssh/ssl.go index 18bff70..2eb3bce 100644 --- a/pkg/apcssh/ssl.go +++ b/pkg/apcssh/ssl.go @@ -1,13 +1,10 @@ package apcssh import ( - "errors" "fmt" "strings" ) -var errSSLMissingData = errors.New("apcssh: ssl cert install: cant install nil data (unsupported key/nmc version/nmc firmware combo?)") - // 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. @@ -32,11 +29,6 @@ func (cli *Client) InstallSSLCert(keyP15 []byte, certPem []byte, keyCertP15 []by // 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 { - // fail if required data isn't present - if keyP15 == nil || len(keyP15) <= 0 || certPem == nil || len(certPem) <= 0 { - return errSSLMissingData - } - // upload the key P15 file err := cli.UploadSCP("/ssl/nmc.key", keyP15, 0600) if err != nil { @@ -71,11 +63,6 @@ func (cli *Client) installSSLCertModern(keyP15 []byte, certPem []byte) error { // 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 { - // fail if required data isn't present - if keyCertP15 == nil || len(keyCertP15) <= 0 { - return errSSLMissingData - } - // upload/install keyCert P15 file err := cli.UploadSCP("/ssl/defaultcert.p15", keyCertP15, 0600) if err != nil { diff --git a/pkg/app/app.go b/pkg/app/app.go index 052a00e..d1f4d6d 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -12,7 +12,7 @@ import ( ) const ( - appVersion = "1.2.2" + appVersion = "0.5.2" ) // struct for receivers to use common app pieces diff --git a/pkg/app/cmd_create.go b/pkg/app/cmd_create.go index 8791a81..77f13ee 100644 --- a/pkg/app/cmd_create.go +++ b/pkg/app/cmd_create.go @@ -51,14 +51,11 @@ func (app *app) cmdCreate(_ context.Context, args []string) error { } app.stdLogger.Printf("create: apc p15 key file %s written to disk", keyFileName) - // skip key+cert if it wasn't generated - if len(apcKeyCertFile) > 0 { - err = os.WriteFile(keyCertFileName, apcKeyCertFile, 0600) - if err != nil { - return fmt.Errorf("create: failed to write apc p15 key+cert file (%s)", err) - } - app.stdLogger.Printf("create: apc p15 key+cert file %s written to disk", keyCertFileName) + err = os.WriteFile(keyCertFileName, apcKeyCertFile, 0600) + if err != nil { + return fmt.Errorf("create: failed to write apc p15 key+cert file (%s)", err) } + app.stdLogger.Printf("create: apc p15 key+cert file %s written to disk", keyCertFileName) // if debug, write additional debug files (b64 format to make copy/paste into asn1 decoder // easy to do e.g., https://lapo.it/asn1js) @@ -70,22 +67,19 @@ func (app *app) cmdCreate(_ context.Context, args []string) error { } app.debugLogger.Printf("create: apc p15 key file %s written to disk", keyFileNameDebug) - // skip key+cert if it wasn't generated - if len(apcKeyCertFile) > 0 { - keyCertFileNameDebug := keyCertFileName + ".noheader.b64" - err = os.WriteFile(keyCertFileNameDebug, []byte(base64.StdEncoding.EncodeToString(apcKeyCertFile[apcHeaderLen:])), 0600) - if err != nil { - return fmt.Errorf("create: failed to write apc p15 key+cert file (%s)", err) - } - app.debugLogger.Printf("create: apc p15 key+cert file %s written to disk", keyCertFileNameDebug) - - keyCertFileNameHeaderDebug := keyCertFileName + ".header.b64" - err = os.WriteFile(keyCertFileNameHeaderDebug, []byte(base64.StdEncoding.EncodeToString(apcKeyCertFile[:apcHeaderLen])), 0600) - if err != nil { - return fmt.Errorf("create: failed to write apc p15 key+cert file (%s)", err) - } - app.debugLogger.Printf("create: apc p15 key+cert file header %s written to disk", keyCertFileNameHeaderDebug) + keyCertFileNameDebug := keyCertFileName + ".noheader.b64" + err = os.WriteFile(keyCertFileNameDebug, []byte(base64.StdEncoding.EncodeToString(apcKeyCertFile[apcHeaderLen:])), 0600) + if err != nil { + return fmt.Errorf("create: failed to write apc p15 key+cert file (%s)", err) } + app.debugLogger.Printf("create: apc p15 key+cert file %s written to disk", keyCertFileNameDebug) + + keyCertFileNameHeaderDebug := keyCertFileName + ".header.b64" + err = os.WriteFile(keyCertFileNameHeaderDebug, []byte(base64.StdEncoding.EncodeToString(apcKeyCertFile[:apcHeaderLen])), 0600) + if err != nil { + return fmt.Errorf("create: failed to write apc p15 key+cert file (%s)", err) + } + app.debugLogger.Printf("create: apc p15 key+cert file header %s written to disk", keyCertFileNameHeaderDebug) } diff --git a/pkg/app/cmd_install.go b/pkg/app/cmd_install.go index eacda53..d3270a4 100644 --- a/pkg/app/cmd_install.go +++ b/pkg/app/cmd_install.go @@ -2,18 +2,11 @@ package app import ( "apc-p15-tool/pkg/apcssh" - "bytes" "context" - "crypto/tls" - "encoding/pem" "errors" "fmt" - "strconv" - "time" ) -const timeLoggingFormat = time.RFC1123Z - // cmdInstall is the app's command to create apc p15 file content from key and cert // pem files and upload the p15 to the specified APC UPS func (app *app) cmdInstall(cmdCtx context.Context, args []string) error { @@ -43,9 +36,7 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error { } // host to install on must be specified - if app.config.install.hostname == nil || *app.config.install.hostname == "" || - app.config.install.sshport == nil || *app.config.install.sshport == 0 { - + if app.config.install.hostAndPort == nil || *app.config.install.hostAndPort == "" { return errors.New("install: failed, apc host not specified") } @@ -64,7 +55,7 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error { // make APC SSH client cfg := &apcssh.Config{ - Hostname: *app.config.install.hostname + ":" + strconv.Itoa(*app.config.install.sshport), + Hostname: *app.config.install.hostAndPort, Username: *app.config.install.username, Password: *app.config.install.password, ServerFingerprint: *app.config.install.fingerprint, @@ -77,16 +68,6 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error { } app.stdLogger.Println("install: connected to ups ssh, installing ssl key and cert...") - // check time - don't fail it time is no good, just do logging here - upsT, err := client.GetTime() - if err != nil { - app.errLogger.Printf("warn: install: failed to fetch UPS time (%s), you should manually verify the time is correct on the UPS", err) - } else if upsT.After(time.Now().Add(1*time.Hour)) || upsT.Before(time.Now().Add(-1*time.Hour)) { - app.errLogger.Printf("warn: install: UPS clock skew detected (this system's time is %s vs. UPS time %s", time.Now().Format(timeLoggingFormat), upsT.Format(timeLoggingFormat)) - } else { - app.stdLogger.Printf("install: UPS clock appears correct (%s)", upsT.Format(timeLoggingFormat)) - } - // install SSL Cert err = client.InstallSSLCert(keyP15, certPem, keyCertP15) if err != nil { @@ -94,7 +75,7 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error { } // installed - app.stdLogger.Printf("install: apc p15 file installed on %s", *app.config.install.hostname) + app.stdLogger.Printf("install: apc p15 file installed on %s", *app.config.install.hostAndPort) // restart UPS webUI if app.config.install.restartWebUI != nil && *app.config.install.restartWebUI { @@ -108,48 +89,5 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error { app.stdLogger.Println("install: sent webui restart command") } - // check the new certificate is installed - if app.config.install.skipVerify != nil && !*app.config.install.skipVerify && - app.config.install.webUISSLPort != nil && *app.config.install.webUISSLPort != 0 { - - app.stdLogger.Println("install: attempting to verify certificate install...") - - // sleep for UPS to finish anything it might be doing - time.Sleep(5 * time.Second) - - // if UPS web UI was restarted, sleep longer - if app.config.install.restartWebUI != nil && *app.config.install.restartWebUI { - app.stdLogger.Println("install: waiting for ups webui restart...") - time.Sleep(25 * time.Second) - } - - // connect to the web UI to get the current certificate - conf := &tls.Config{ - InsecureSkipVerify: true, - } - conn, err := tls.Dial("tcp", *app.config.install.hostname+":"+strconv.Itoa(*app.config.install.webUISSLPort), conf) - if err != nil { - return fmt.Errorf("install: failed to dial webui for verification (%s)", err) - } - defer conn.Close() - - // get top cert - leafCert := conn.ConnectionState().PeerCertificates[0] - if leafCert == nil { - return fmt.Errorf("install: failed to get web ui leaf cert for verification (%s)", err) - } - - // convert pem to DER for comparison - pemBlock, _ := pem.Decode(certPem) - - // verify cert is the correct one - certVerified := bytes.Equal(leafCert.Raw, pemBlock.Bytes) - if !certVerified { - return errors.New("install: web ui leaf cert does not match new cert") - } - - app.stdLogger.Println("install: ups web ui cert verified") - } - return nil } diff --git a/pkg/app/config.go b/pkg/app/config.go index 7edf216..e8ff1fc 100644 --- a/pkg/app/config.go +++ b/pkg/app/config.go @@ -33,14 +33,11 @@ type config struct { } install struct { keyCertPemCfg - hostname *string - sshport *int + hostAndPort *string fingerprint *string username *string password *string restartWebUI *bool - webUISSLPort *int - skipVerify *bool insecureCipher *bool } } @@ -71,9 +68,9 @@ func (app *app) getConfig(args []string) error { // create -- subcommand createFlags := ff.NewFlagSet("create").SetParent(rootFlags) - cfg.create.keyPemFilePath = createFlags.StringLong("keyfile", "", "path and filename of the key in pem format") + cfg.create.keyPemFilePath = createFlags.StringLong("keyfile", "", "path and filename of the rsa-1024 or rsa-2048 key in pem format") cfg.create.certPemFilePath = createFlags.StringLong("certfile", "", "path and filename of the certificate in pem format") - cfg.create.keyPem = createFlags.StringLong("keypem", "", "string of the key in pem format") + cfg.create.keyPem = createFlags.StringLong("keypem", "", "string of the rsa-1024 or rsa-2048 key in pem format") cfg.create.certPem = createFlags.StringLong("certpem", "", "string of the certificate in pem format") cfg.create.outFilePath = createFlags.StringLong("outfile", createDefaultOutFilePath, "path and filename to write the key+cert p15 file to") cfg.create.outKeyFilePath = createFlags.StringLong("outkeyfile", createDefaultOutKeyFilePath, "path and filename to write the key p15 file to") @@ -91,23 +88,20 @@ func (app *app) getConfig(args []string) error { // install -- subcommand installFlags := ff.NewFlagSet("install").SetParent(rootFlags) - cfg.install.keyPemFilePath = installFlags.StringLong("keyfile", "", "path and filename of the key in pem format") + cfg.install.keyPemFilePath = installFlags.StringLong("keyfile", "", "path and filename of the rsa-1024 or rsa-2048 key in pem format") cfg.install.certPemFilePath = installFlags.StringLong("certfile", "", "path and filename of the certificate in pem format") - cfg.install.keyPem = installFlags.StringLong("keypem", "", "string of the key in pem format") + cfg.install.keyPem = installFlags.StringLong("keypem", "", "string of the rsa-1024 or rsa-2048 key in pem format") cfg.install.certPem = installFlags.StringLong("certpem", "", "string of the certificate in pem format") - cfg.install.hostname = installFlags.StringLong("hostname", "", "hostname of the apc ups to install the certificate on") - cfg.install.sshport = installFlags.IntLong("sshport", 22, "apc ups ssh port number") + cfg.install.hostAndPort = installFlags.StringLong("apchost", "", "hostname:port of the apc ups to install the certificate on") cfg.install.fingerprint = installFlags.StringLong("fingerprint", "", "the SHA256 fingerprint value of the ups' ssh server") cfg.install.username = installFlags.StringLong("username", "", "username to login to the apc ups") cfg.install.password = installFlags.StringLong("password", "", "password to login to the apc ups") cfg.install.restartWebUI = installFlags.BoolLong("restartwebui", "some devices may need a webui restart to begin using the new cert, enabling this option sends the restart command after the p15 is installed") - cfg.install.webUISSLPort = installFlags.IntLong("sslport", 443, "apc ups ssl webui port number") - cfg.install.skipVerify = installFlags.BoolLong("skipverify", "the tool will try to connect to the UPS web UI to verify install success; this flag disables that check") cfg.install.insecureCipher = installFlags.BoolLong("insecurecipher", "allows the use of insecure ssh ciphers (NOT recommended)") installCmd := &ff.Command{ Name: "install", - Usage: "apc-p15-tool install --keyfile key.pem --certfile cert.pem --hostname example.com --fingerprint 123abc --username apc --password test", + Usage: "apc-p15-tool install --keyfile key.pem --certfile cert.pem --apchost example.com:22 --fingerprint 123abc --username apc --password test", ShortHelp: "install the specified key and cert pem files on an apc ups (they will be converted to a comaptible p15 file)", Flags: installFlags, Exec: app.cmdInstall, diff --git a/pkg/app/pem_to_p15.go b/pkg/app/pem_to_p15.go index d48d4a8..e376bc6 100644 --- a/pkg/app/pem_to_p15.go +++ b/pkg/app/pem_to_p15.go @@ -3,22 +3,13 @@ package app import ( "apc-p15-tool/pkg/pkcs15" "fmt" - "slices" ) -// list of keys supported by the NMC2 -var nmc2SupportedKeyTypes = []pkcs15.KeyType{ - pkcs15.KeyTypeRSA1024, - pkcs15.KeyTypeRSA2048, - pkcs15.KeyTypeRSA3072, // officially not supported but works -} - -// pemToAPCP15 reads the specified pem files and returns the apc p15 file(s). If the -// key type of the key is not supported by NMC2, the combined key+cert file is not -// generated and nil is returned instead for that file. If the key IS supported by -// NMC2, the key+cert file is generated and the proper header is prepended. +// 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) pemToAPCP15(keyPem, certPem []byte, parentCmdName string) (keyFile []byte, apcKeyCertFile []byte, err error) { - app.stdLogger.Printf("%s: making apc p15 file(s) content from pem", parentCmdName) + app.stdLogger.Printf("%s: making apc p15 file from pem", parentCmdName) // make p15 struct p15, err := pkcs15.ParsePEMToPKCS15(keyPem, certPem) @@ -26,40 +17,24 @@ func (app *app) pemToAPCP15(keyPem, certPem []byte, parentCmdName string) (keyFi return nil, nil, fmt.Errorf("%s: failed to parse pem files (%w)", parentCmdName, err) } - app.stdLogger.Printf("%s: successfully parsed pem files", parentCmdName) + app.stdLogger.Printf("%s: successfully loaded pem files", parentCmdName) - // make key file (always) - keyFile, err = p15.ToP15Key() + // make file bytes + keyCertFile, keyFile, err := p15.ToP15Files() if err != nil { - return nil, nil, fmt.Errorf("%s: failed to make p15 key file (%w)", parentCmdName, err) + return nil, nil, fmt.Errorf("%s: failed to make p15 file (%w)", parentCmdName, err) } - app.stdLogger.Printf("%s: successfully generated p15 key file content", parentCmdName) - - // check key type for compat with NMC2 - if slices.Contains(nmc2SupportedKeyTypes, p15.KeyType()) { - app.stdLogger.Printf("%s: key type is supported by NMC2, generating p15 key+cert file content...", parentCmdName) - - // make file bytes - keyCertFile, err := p15.ToP15KeyCert() - if err != nil { - return nil, nil, fmt.Errorf("%s: failed to make p15 key+cert file content (%w)", parentCmdName, err) - } - - // make header for file bytes - apcHeader, err := makeFileHeader(keyCertFile) - if err != nil { - return nil, nil, fmt.Errorf("%s: failed to make p15 key+cert file header (%w)", parentCmdName, err) - } - - // combine header with file - apcKeyCertFile = append(apcHeader, keyCertFile...) - } else { - // NMC2 unsupported - app.stdLogger.Printf("%s: key type is not supported by NMC2, skipping p15 key+cert file content", parentCmdName) + // make header for file bytes + apcHeader, err := makeFileHeader(keyCertFile) + if err != nil { + return nil, nil, fmt.Errorf("%s: failed to make p15 file header (%w)", parentCmdName, err) } - app.stdLogger.Printf("%s: apc p15 file(s) data succesfully generated", parentCmdName) + // combine header with file + apcKeyCertFile = append(apcHeader, keyCertFile...) + + app.stdLogger.Printf("%s: apc p15 file data succesfully generated", parentCmdName) return keyFile, apcKeyCertFile, nil } diff --git a/pkg/pkcs15/encrypted_envelope.go b/pkg/pkcs15/encrypted_envelope.go index 71433d1..cead3c8 100644 --- a/pkg/pkcs15/encrypted_envelope.go +++ b/pkg/pkcs15/encrypted_envelope.go @@ -21,19 +21,14 @@ const ( apcKEKIterations = 5000 ) -// encryptedKeyEnvelope encrypts p15's private key using the algorithms and -// params expected in the APC file. -func (p15 *pkcs15KeyCert) computeEncryptedKeyEnvelope() error { - // if computation already performed, this is a no-op (keep existing envelope) - if p15.envelopedPrivateKey != nil && len(p15.envelopedPrivateKey) != 0 { - return nil - } - +// encryptedKeyEnvelope encrypts p15's rsa private key using the algorithms and +// params expected in the APC file. Salt values are always random. +func (p15 *pkcs15KeyCert) encryptedKeyEnvelope() ([]byte, error) { // calculate values for the object kekSalt := make([]byte, 8) _, err := rand.Read(kekSalt) if err != nil { - return err + return nil, err } // kek hash alg @@ -47,7 +42,7 @@ func (p15 *pkcs15KeyCert) computeEncryptedKeyEnvelope() error { // make DES cipher from KEK for CEK cekDesCipher, err := des.NewTripleDESCipher(kek) if err != nil { - return err + return nil, err } // cek (16 bytes for authEnc128) -- see: rfc3211 @@ -55,7 +50,7 @@ func (p15 *pkcs15KeyCert) computeEncryptedKeyEnvelope() error { cek := make([]byte, cekLen) _, err = rand.Read(cek) if err != nil { - return err + return nil, err } // LEN + Check Val [3] @@ -76,7 +71,7 @@ func (p15 *pkcs15KeyCert) computeEncryptedKeyEnvelope() error { cekPadding := make([]byte, cekPadLen) _, err = rand.Read(cekPadding) if err != nil { - return err + return nil, err } wrappedCEK = append(wrappedCEK, cekPadding...) @@ -85,7 +80,7 @@ func (p15 *pkcs15KeyCert) computeEncryptedKeyEnvelope() error { cekEncryptSalt := make([]byte, 8) _, err = rand.Read(cekEncryptSalt) if err != nil { - return err + return nil, err } cekEncrypter := cipher.NewCBCEncrypter(cekDesCipher, cekEncryptSalt) @@ -99,13 +94,13 @@ func (p15 *pkcs15KeyCert) computeEncryptedKeyEnvelope() error { contentEncSalt := make([]byte, 8) _, err = rand.Read(contentEncSalt) if err != nil { - return err + return nil, err } contentEncryptKey := pbkdf2.Key(cek, []byte("encryption"), 1, 24, sha1.New) contentDesCipher, err := des.NewTripleDESCipher(contentEncryptKey) if err != nil { - return err + return nil, err } // envelope content (that will be encrypted) @@ -156,7 +151,7 @@ func (p15 *pkcs15KeyCert) computeEncryptedKeyEnvelope() error { // make MAC _, err = macHasher.Write(hashMe) if err != nil { - return err + return nil, err } mac := macHasher.Sum(nil) @@ -223,7 +218,5 @@ func (p15 *pkcs15KeyCert) computeEncryptedKeyEnvelope() error { finalEnv = append(finalEnv, envelope[i]...) } - // set p15 struct envelope - p15.envelopedPrivateKey = finalEnv - return nil + return finalEnv, nil } diff --git a/pkg/pkcs15/keyid.go b/pkg/pkcs15/keyid.go index 08a3ce4..76f4297 100644 --- a/pkg/pkcs15/keyid.go +++ b/pkg/pkcs15/keyid.go @@ -2,8 +2,6 @@ package pkcs15 import ( "apc-p15-tool/pkg/tools/asn1obj" - "crypto/ecdsa" - "crypto/rsa" "crypto/sha1" "encoding/binary" "math/big" @@ -13,6 +11,22 @@ import ( func (p15 *pkcs15KeyCert) keyId() []byte { // object to hash is just the RawSubjectPublicKeyInfo + // Create Object to hash + // hashObj := asn1obj.Sequence([][]byte{ + // asn1obj.Sequence([][]byte{ + // // Key is RSA + // asn1obj.ObjectIdentifier(asn1obj.OIDrsaEncryptionPKCS1), + // asn1.NullBytes, + // }), + // // BIT STRING of rsa key public key + // asn1obj.BitString( + // asn1obj.Sequence([][]byte{ + // asn1obj.Integer(p15.key.N), + // asn1obj.Integer((big.NewInt(int64(p15.key.E)))), + // }), + // ), + // }) + // SHA-1 Hash hasher := sha1.New() _, err := hasher.Write(p15.cert.RawSubjectPublicKeyInfo) @@ -110,32 +124,18 @@ func (p15 *pkcs15KeyCert) keyIdInt7() []byte { } // keyIdInt8 returns the sequence for keyId with INT val of 8; This value is equivelant -// to "pgp", which is PGP v3 key Id. +// to "pgp", which is PGP v3 key Id. This value is just the last 8 bytes of the public +// key N value func (p15 *pkcs15KeyCert) keyIdInt8() []byte { - var keyIdVal []byte - - switch privKey := p15.key.(type) { - case *rsa.PrivateKey: - // RSA: The ID value is just the last 8 bytes of the public key N value - nBytes := privKey.N.Bytes() - keyIdVal = nBytes[len(nBytes)-8:] - - case *ecdsa.PrivateKey: - // don't use this key id, leave empty - return nil - - default: - // panic if unexpected key type - panic("key id 8 for key is unexpected and unsupported") - } + nBytes := p15.key.N.Bytes() // object to return - idObj := asn1obj.Sequence([][]byte{ + obj := asn1obj.Sequence([][]byte{ asn1obj.Integer(big.NewInt(8)), - asn1obj.OctetString(keyIdVal), + asn1obj.OctetString(nBytes[len(nBytes)-8:]), }) - return idObj + return obj } // bigIntToMpi returns the MPI (as defined in RFC 4880 s 3.2) from a given @@ -156,44 +156,33 @@ func (p15 *pkcs15KeyCert) keyIdInt9() []byte { // Public-Key packet starting with the version field. The Key ID is the // low-order 64 bits of the fingerprint. - // first make the public key packet + // the entire Public-Key packet publicKeyPacket := []byte{} // starting with the version field (A one-octet version number (4)). publicKeyPacket = append(publicKeyPacket, byte(4)) // A four-octet number denoting the time that the key was created. + time := make([]byte, 4) + // NOTE: use cert validity start as proxy for key creation since key pem // doesn't actually contain a created at time -- in reality notBefore tends // to be ~ 1 hour ish BEFORE the cert was even created. Key would also // obviously have to be created prior to the cert creation. - time := make([]byte, 4) binary.BigEndian.PutUint32(time, uint32(p15.cert.NotBefore.Unix())) publicKeyPacket = append(publicKeyPacket, time...) - // the next part is key type specific - switch privKey := p15.key.(type) { - case *rsa.PrivateKey: - // A one-octet number denoting the public-key algorithm of this key. - // 1 - RSA (Encrypt or Sign) [HAC] - publicKeyPacket = append(publicKeyPacket, byte(1)) + // A one-octet number denoting the public-key algorithm of this key. + // 1 - RSA (Encrypt or Sign) [HAC] + publicKeyPacket = append(publicKeyPacket, byte(1)) - // Algorithm-Specific Fields for RSA public keys: - // multiprecision integer (MPI) of RSA public modulus n - publicKeyPacket = append(publicKeyPacket, bigIntToMpi(privKey.N)...) + // Algorithm-Specific Fields for RSA public keys: + // multiprecision integer (MPI) of RSA public modulus n + publicKeyPacket = append(publicKeyPacket, bigIntToMpi(p15.key.N)...) - // MPI of RSA public encryption exponent e - e := big.NewInt(int64(privKey.PublicKey.E)) - publicKeyPacket = append(publicKeyPacket, bigIntToMpi(e)...) - - case *ecdsa.PrivateKey: - // don't use this key id, leave empty - return nil - - default: - // panic if unexpected key type - panic("key id 9 for key is unexpected and unsupported") - } + // MPI of RSA public encryption exponent e + e := big.NewInt(int64(p15.key.PublicKey.E)) + publicKeyPacket = append(publicKeyPacket, bigIntToMpi(e)...) // Assemble the V4 byte array that will be hashed // 0x99 (1 octet) @@ -216,10 +205,10 @@ func (p15 *pkcs15KeyCert) keyIdInt9() []byte { keyId := sha1Hash[len(sha1Hash)-8:] // object to return - idObj := asn1obj.Sequence([][]byte{ + obj := asn1obj.Sequence([][]byte{ asn1obj.Integer(big.NewInt(9)), asn1obj.OctetString(keyId), }) - return idObj + return obj } diff --git a/pkg/pkcs15/pem_decode.go b/pkg/pkcs15/pem_decode.go index 2d51837..d4b8764 100644 --- a/pkg/pkcs15/pem_decode.go +++ b/pkg/pkcs15/pem_decode.go @@ -1,37 +1,28 @@ package pkcs15 import ( - "crypto" - "crypto/ecdsa" "crypto/rsa" "crypto/tls" "crypto/x509" "encoding/pem" "errors" "fmt" - "reflect" - "slices" ) var ( errPemKeyBadBlock = errors.New("pkcs15: pem key: failed to decode pem block") errPemKeyFailedToParse = errors.New("pkcs15: pem key: failed to parse key") - errPemKeyWrongBlockType = errors.New("pkcs15: pem key: unsupported pem block type") - errKeyWrongType = errors.New("pkcs15: pem key: unsupported key type") + errPemKeyWrongBlockType = errors.New("pkcs15: pem key: unsupported pem block type (only pkcs1 and pkcs8 supported)") + errPemKeyWrongType = errors.New("pkcs15: pem key: unsupported key type (only rsa 1,024 or 2,048 supported)") errPemCertBadBlock = errors.New("pkcs15: pem cert: failed to decode pem block") errPemCertFailedToParse = errors.New("pkcs15: pem cert: failed to parse cert") ) -var ( - supportedRSASizes = []int{1024, 2048, 3072, 4096} - supportedECDSACurves = []string{"P-256", "P-384", "P-521"} -) - // pemKeyDecode attempts to decode a pem encoded byte slice and then attempts -// to parse a private key from the decoded pem block. an error is returned -// if any of these steps fail OR if the key is not supported. -func pemKeyDecode(keyPem []byte) (crypto.PrivateKey, error) { +// to parse an RSA private key from the decoded pem block. an error is returned +// if any of these steps fail OR if the key is not RSA and of bitlen 1,024 or 2,048 +func pemKeyDecode(keyPem []byte) (*rsa.PrivateKey, error) { // decode pemBlock, _ := pem.Decode([]byte(keyPem)) if pemBlock == nil { @@ -39,11 +30,13 @@ func pemKeyDecode(keyPem []byte) (crypto.PrivateKey, error) { } // parsing depends on block type - var privateKey crypto.PrivateKey + var rsaKey *rsa.PrivateKey switch pemBlock.Type { case "RSA PRIVATE KEY": // PKCS1 - rsaKey, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes) + var err error + + rsaKey, err = x509.ParsePKCS1PrivateKey(pemBlock.Bytes) if err != nil { return nil, errPemKeyFailedToParse } @@ -54,27 +47,12 @@ func pemKeyDecode(keyPem []byte) (crypto.PrivateKey, error) { return nil, fmt.Errorf("pkcs15: pem key: failed sanity check (%s)", err) } - // verify supported rsa bitlen - if !slices.Contains(supportedRSASizes, rsaKey.N.BitLen()) { - return nil, errKeyWrongType + // verify proper bitlen + if rsaKey.N.BitLen() != 1024 && rsaKey.N.BitLen() != 2048 { + return nil, errPemKeyWrongType } // good to go - privateKey = rsaKey - - case "EC PRIVATE KEY": // SEC1, ASN.1 - ecdKey, err := x509.ParseECPrivateKey(pemBlock.Bytes) - if err != nil { - return nil, errPemKeyFailedToParse - } - - // verify supported curve name - if !slices.Contains(supportedECDSACurves, ecdKey.Curve.Params().Name) { - return nil, errKeyWrongType - } - - // good to go - privateKey = ecdKey case "PRIVATE KEY": // PKCS8 pkcs8Key, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes) @@ -84,31 +62,23 @@ func pemKeyDecode(keyPem []byte) (crypto.PrivateKey, error) { switch pkcs8Key := pkcs8Key.(type) { case *rsa.PrivateKey: + rsaKey = pkcs8Key + // basic sanity check - err = pkcs8Key.Validate() + err = rsaKey.Validate() if err != nil { return nil, fmt.Errorf("pkcs15: pem key: failed sanity check (%s)", err) } - // verify supported rsa bitlen - if !slices.Contains(supportedRSASizes, pkcs8Key.N.BitLen()) { - return nil, errKeyWrongType + // verify proper bitlen + if rsaKey.N.BitLen() != 1024 && rsaKey.N.BitLen() != 2048 { + return nil, errPemKeyWrongType } // good to go - privateKey = pkcs8Key - - case *ecdsa.PrivateKey: - // verify supported curve name - if !slices.Contains(supportedECDSACurves, pkcs8Key.Curve.Params().Name) { - return nil, errKeyWrongType - } - - // good to go - privateKey = pkcs8Key default: - return nil, errKeyWrongType + return nil, errPemKeyWrongType } default: @@ -116,12 +86,12 @@ func pemKeyDecode(keyPem []byte) (crypto.PrivateKey, error) { } // if rsaKey is nil somehow, error - if reflect.ValueOf(privateKey).IsNil() { + if rsaKey == nil { return nil, errors.New("pkcs15: pem key: rsa key unexpectedly nil (report bug to project repo)") } // success! - return privateKey, nil + return rsaKey, nil } // pemCertDecode attempts to decode a pem encoded byte slice and then attempts diff --git a/pkg/pkcs15/pem_parse.go b/pkg/pkcs15/pem_parse.go index 19e44f1..dcd899a 100644 --- a/pkg/pkcs15/pem_parse.go +++ b/pkg/pkcs15/pem_parse.go @@ -1,8 +1,6 @@ package pkcs15 import ( - "crypto" - "crypto/ecdsa" "crypto/rsa" "crypto/x509" ) @@ -10,61 +8,8 @@ import ( // pkcs15KeyCert holds the data for a key and certificate pair; it provides // various methods to transform pkcs15 data type pkcs15KeyCert struct { - key crypto.PrivateKey + key *rsa.PrivateKey cert *x509.Certificate - // store the encrypted enveloped Private Key for re-use - envelopedPrivateKey []byte -} - -// KeyType is used by consumers to check for compatibility -type KeyType int - -const ( - KeyTypeRSA1024 KeyType = iota - KeyTypeRSA2048 - KeyTypeRSA3072 - KeyTypeRSA4096 - - KeyTypeECP256 - KeyTypeECP384 - KeyTypeECP521 - - KeyTypeUnknown -) - -// KeyType returns the private key type -func (p15 *pkcs15KeyCert) KeyType() KeyType { - switch pKey := p15.key.(type) { - case *rsa.PrivateKey: - switch pKey.N.BitLen() { - case 1024: - return KeyTypeRSA1024 - case 2048: - return KeyTypeRSA2048 - case 3072: - return KeyTypeRSA3072 - case 4096: - return KeyTypeRSA4096 - - default: - } - - case *ecdsa.PrivateKey: - switch pKey.Curve.Params().Name { - case "P-256": - return KeyTypeECP256 - case "P-384": - return KeyTypeECP384 - case "P-521": - return KeyTypeECP521 - - default: - } - - default: - } - - return KeyTypeUnknown } // ParsePEMToPKCS15 parses the provide pem files to a pkcs15 struct; it also does some @@ -82,17 +27,10 @@ func ParsePEMToPKCS15(keyPem, certPem []byte) (*pkcs15KeyCert, error) { return nil, err } - // create p15 struct p15 := &pkcs15KeyCert{ key: key, cert: cert, } - // pre-calculate encrypted envelope - err = p15.computeEncryptedKeyEnvelope() - if err != nil { - return nil, err - } - return p15, nil } diff --git a/pkg/pkcs15/pem_to_p15.go b/pkg/pkcs15/pem_to_p15.go index 0c2214d..19392f2 100644 --- a/pkg/pkcs15/pem_to_p15.go +++ b/pkg/pkcs15/pem_to_p15.go @@ -2,10 +2,7 @@ package pkcs15 import ( "apc-p15-tool/pkg/tools/asn1obj" - "crypto/ecdsa" - "crypto/rsa" "encoding/asn1" - "fmt" "math/big" ) @@ -15,87 +12,39 @@ const ( // toP15KeyCert creates a P15 file with both the private key and certificate, mirroring the // final p15 file an APC UPS expects (though without the header) -func (p15 *pkcs15KeyCert) ToP15KeyCert() (keyCert []byte, err error) { - // encrypted envelope is required - err = p15.computeEncryptedKeyEnvelope() - if err != nil { - return nil, err - } - - // create private key object - var privKeyObj []byte - - switch p15.key.(type) { - case *rsa.PrivateKey: - // private key object - privKeyObj = +func (p15 *pkcs15KeyCert) toP15KeyCert(keyEnvelope []byte) (keyCert []byte, err error) { + // private key object + privateKey := asn1obj.Sequence([][]byte{ + // commonObjectAttributes - Label + asn1obj.Sequence([][]byte{ + asn1obj.UTF8String(apcKeyLabel), + }), + // CommonKeyAttributes + asn1obj.Sequence([][]byte{ + // CommonKeyAttributes - iD - uses keyId that is SHA1( SubjectPublicKeyInfo SEQUENCE ) + asn1obj.OctetString(p15.keyId()), + // CommonKeyAttributes - usage (trailing 0s will drop) + asn1obj.BitString([]byte{byte(0b11100010)}), + // CommonKeyAttributes - accessFlags (trailing 0s will drop) + asn1obj.BitString([]byte{byte(0b10110000)}), + // CommonKeyAttributes - startDate + asn1obj.GeneralizedTime(p15.cert.NotBefore), + // CommonKeyAttributes - [0] endDate + asn1obj.GeneralizedTimeExplicitValue(0, p15.cert.NotAfter), + }), + // ObjectValue - indirect-protected + asn1obj.ExplicitCompound(1, [][]byte{ asn1obj.Sequence([][]byte{ - // commonObjectAttributes - Label - asn1obj.Sequence([][]byte{ - asn1obj.UTF8String(apcKeyLabel), + // AuthEnvelopedData Type ([4]) + asn1obj.ExplicitCompound(4, [][]byte{ + keyEnvelope, }), - // CommonKeyAttributes - asn1obj.Sequence([][]byte{ - // CommonKeyAttributes - iD - uses keyId that is SHA1( SubjectPublicKeyInfo SEQUENCE ) - asn1obj.OctetString(p15.keyId()), - // CommonKeyAttributes - usage (trailing 0s will drop) - asn1obj.BitString([]byte{byte(0b11100010)}), - // CommonKeyAttributes - accessFlags (trailing 0s will drop) - asn1obj.BitString([]byte{byte(0b10110000)}), - // CommonKeyAttributes - startDate - asn1obj.GeneralizedTime(p15.cert.NotBefore), - // CommonKeyAttributes - [0] endDate - asn1obj.GeneralizedTimeExplicitValue(0, p15.cert.NotAfter), - }), - // ObjectValue - indirect-protected - asn1obj.ExplicitCompound(1, [][]byte{ - asn1obj.Sequence([][]byte{ - // AuthEnvelopedData Type ([4]) - asn1obj.ExplicitCompound(4, [][]byte{ - p15.envelopedPrivateKey, - }), - }), - }), - }) - - case *ecdsa.PrivateKey: - privKeyObj = - asn1obj.ExplicitCompound(0, [][]byte{ - // commonObjectAttributes - Label - asn1obj.Sequence([][]byte{ - asn1obj.UTF8String(apcKeyLabel), - }), - // CommonKeyAttributes - asn1obj.Sequence([][]byte{ - // CommonKeyAttributes - iD - uses keyId that is SHA1( SubjectPublicKeyInfo SEQUENCE ) - asn1obj.OctetString(p15.keyId()), - // CommonKeyAttributes - usage (trailing 0s will drop) - asn1obj.BitString([]byte{byte(0b00100010)}), - // CommonKeyAttributes - accessFlags (trailing 0s will drop) - asn1obj.BitString([]byte{byte(0b10110000)}), - // CommonKeyAttributes - startDate - asn1obj.GeneralizedTime(p15.cert.NotBefore), - // CommonKeyAttributes - [0] endDate - asn1obj.GeneralizedTimeExplicitValue(0, p15.cert.NotAfter), - }), - // ObjectValue - indirect-protected - asn1obj.ExplicitCompound(1, [][]byte{ - asn1obj.Sequence([][]byte{ - // AuthEnvelopedData Type ([4]) - asn1obj.ExplicitCompound(4, [][]byte{ - p15.envelopedPrivateKey, - }), - }), - }), - }) - - default: - // bad key type - return nil, errKeyWrongType - } + }), + }), + }) // cert object - certObj := asn1obj.Sequence([][]byte{ + cert := asn1obj.Sequence([][]byte{ // commonObjectAttributes - Label asn1obj.Sequence([][]byte{ asn1obj.UTF8String(apcKeyLabel), @@ -109,7 +58,6 @@ func (p15 *pkcs15KeyCert) ToP15KeyCert() (keyCert []byte, err error) { p15.keyIdInt3(), p15.keyIdInt6(), p15.keyIdInt7(), - // 8 & 9 will return nil for EC keys (effectively omitting them) p15.keyIdInt8(), p15.keyIdInt9(), }), @@ -128,7 +76,7 @@ func (p15 *pkcs15KeyCert) ToP15KeyCert() (keyCert []byte, err error) { }), }) - // build the object + // build the file // ContentInfo keyCert = asn1obj.Sequence([][]byte{ @@ -143,12 +91,12 @@ func (p15 *pkcs15KeyCert) ToP15KeyCert() (keyCert []byte, err error) { asn1obj.Sequence([][]byte{ asn1obj.ExplicitCompound(0, [][]byte{ asn1obj.ExplicitCompound(0, [][]byte{ - privKeyObj, + privateKey, }), }), asn1obj.ExplicitCompound(4, [][]byte{ asn1obj.ExplicitCompound(0, [][]byte{ - certObj, + cert, }), }), }), @@ -162,212 +110,129 @@ func (p15 *pkcs15KeyCert) ToP15KeyCert() (keyCert []byte, err error) { // toP15Key creates a P15 file with just the private key, mirroring the p15 format // the APC tool uses when generating a new private key (Note: no header is used on // this file) -func (p15 *pkcs15KeyCert) ToP15Key() (key []byte, err error) { - // encrypted envelope is required - err = p15.computeEncryptedKeyEnvelope() - if err != nil { - return nil, err - } +func (p15 *pkcs15KeyCert) toP15Key(keyEnvelope []byte) (key []byte, err error) { + // private key object (slightly different than the key+cert format) + privateKey := asn1obj.Sequence([][]byte{ + // commonObjectAttributes - Label + asn1obj.Sequence([][]byte{ + asn1obj.UTF8String(apcKeyLabel), + }), + // CommonKeyAttributes + asn1obj.Sequence([][]byte{ + // CommonKeyAttributes - iD - uses keyId that is SHA1( SubjectPublicKeyInfo SEQUENCE ) + asn1obj.OctetString(p15.keyId()), + // CommonKeyAttributes - usage (trailing 0s will drop) + asn1obj.BitString([]byte{byte(0b11100010)}), + // CommonKeyAttributes - accessFlags (trailing 0s will drop) + asn1obj.BitString([]byte{byte(0b10110000)}), + }), - // create private and public key objects - var pubKeyObj, privKeyObj []byte - - switch privKey := p15.key.(type) { - case *rsa.PrivateKey: - // private key object (slightly different than the key+cert format) - privKeyObj = + // + asn1obj.ExplicitCompound(0, [][]byte{ asn1obj.Sequence([][]byte{ - // commonObjectAttributes - Label - asn1obj.Sequence([][]byte{ - asn1obj.UTF8String(apcKeyLabel), - }), - // CommonKeyAttributes - asn1obj.Sequence([][]byte{ - // CommonKeyAttributes - iD - uses keyId that is SHA1( SubjectPublicKeyInfo SEQUENCE ) - asn1obj.OctetString(p15.keyId()), - // CommonKeyAttributes - usage (trailing 0s will drop) - asn1obj.BitString([]byte{byte(0b11100010)}), - // CommonKeyAttributes - accessFlags (trailing 0s will drop) - asn1obj.BitString([]byte{byte(0b10110000)}), - }), - - // Key IDs asn1obj.ExplicitCompound(0, [][]byte{ - asn1obj.Sequence([][]byte{ - asn1obj.ExplicitCompound(0, [][]byte{ - p15.keyIdInt2(), - p15.keyIdInt8(), - p15.keyIdInt9(), - }), - }), + p15.keyIdInt2(), + p15.keyIdInt8(), + p15.keyIdInt9(), }), + }), + }), - // ObjectValue - indirect-protected - asn1obj.ExplicitCompound(1, [][]byte{ - asn1obj.Sequence([][]byte{ - // AuthEnvelopedData Type ([4]) - asn1obj.ExplicitCompound(4, [][]byte{ - p15.envelopedPrivateKey, - }), - }), - }), - }) - - // pub key stub - pubKeyObj = + // ObjectValue - indirect-protected + asn1obj.ExplicitCompound(1, [][]byte{ asn1obj.Sequence([][]byte{ - // commonObjectAttributes - Label - asn1obj.Sequence([][]byte{ - asn1obj.UTF8String(apcKeyLabel), - }), - // CommonKeyAttributes - asn1obj.Sequence([][]byte{ - asn1obj.OctetString(p15.keyId()), - asn1obj.BitString([]byte{byte(0b10000010)}), - asn1obj.BitString([]byte{byte(0b01000000)}), + // AuthEnvelopedData Type ([4]) + asn1obj.ExplicitCompound(4, [][]byte{ + keyEnvelope, }), + }), + }), + }) - asn1obj.ExplicitCompound(1, [][]byte{ - asn1obj.Sequence([][]byte{ + // ContentInfo + key = asn1obj.Sequence([][]byte{ + + // contentType: OID: 1.2.840.113549.1.15.3.1 pkcs15content (PKCS #15 content type) + asn1obj.ObjectIdentifier(asn1obj.OIDPkscs15Content), + + // content + asn1obj.ExplicitCompound(0, [][]byte{ + asn1obj.Sequence([][]byte{ + asn1obj.Integer(big.NewInt(0)), + asn1obj.Sequence([][]byte{ + // [0] Private Key + asn1obj.ExplicitCompound(0, [][]byte{ asn1obj.ExplicitCompound(0, [][]byte{ - asn1obj.ExplicitCompound(1, [][]byte{ - asn1obj.Sequence([][]byte{ - asn1obj.ObjectIdentifier(asn1obj.OIDrsaEncryptionPKCS1), - asn1.NullBytes, - }), - // RSAPublicKey SubjectPublicKeyInfo - asn1obj.BitString( - asn1obj.Sequence([][]byte{ - asn1obj.Integer(privKey.PublicKey.N), - asn1obj.Integer(big.NewInt(int64(privKey.PublicKey.E))), - }), - ), - }), - }), - // not 100% certain but appears to be rsa key byte len - asn1obj.Integer(big.NewInt(int64(privKey.PublicKey.N.BitLen() / 8))), - }), - }), - }) - - case *ecdsa.PrivateKey: - // private key object (slightly different than the key+cert format) - privKeyObj = - asn1obj.ExplicitCompound(0, [][]byte{ - // commonObjectAttributes - Label - asn1obj.Sequence([][]byte{ - asn1obj.UTF8String(apcKeyLabel), - }), - // CommonKeyAttributes - asn1obj.Sequence([][]byte{ - // CommonKeyAttributes - iD - uses keyId that is SHA1( SubjectPublicKeyInfo SEQUENCE ) - asn1obj.OctetString(p15.keyId()), - // CommonKeyAttributes - usage (trailing 0s will drop) - asn1obj.BitString([]byte{byte(0b00100010)}), - // CommonKeyAttributes - accessFlags (trailing 0s will drop) - asn1obj.BitString([]byte{byte(0b10110000)}), - }), - - // Key IDs - asn1obj.ExplicitCompound(0, [][]byte{ - asn1obj.Sequence([][]byte{ - asn1obj.ExplicitCompound(0, [][]byte{ - p15.keyIdInt2(), + privateKey, }), }), - }), - - // ObjectValue - indirect-protected - asn1obj.ExplicitCompound(1, [][]byte{ - asn1obj.Sequence([][]byte{ - // AuthEnvelopedData Type ([4]) - asn1obj.ExplicitCompound(4, [][]byte{ - p15.envelopedPrivateKey, - }), - }), - }), - }) - - // convert ec pub key to a form that provides a public key bytes function - ecdhKey, err := privKey.PublicKey.ECDH() - if err != nil { - return nil, fmt.Errorf("failed to parse ec public key (%s)", err) - } - - // select correct OID for curve - var curveOID asn1.ObjectIdentifier - switch privKey.Curve.Params().Name { - case "P-256": - curveOID = asn1obj.OIDprime256v1 - case "P-384": - curveOID = asn1obj.OIDsecp384r1 - case "P-521": - curveOID = asn1obj.OIDsecp521r1 - default: - // bad curve name - return nil, errKeyWrongType - } - - // pub key stub - pubKeyObj = - asn1obj.ExplicitCompound(0, [][]byte{ - // commonObjectAttributes - Label - asn1obj.Sequence([][]byte{ - asn1obj.UTF8String(apcKeyLabel), - }), - // CommonKeyAttributes - asn1obj.Sequence([][]byte{ - asn1obj.OctetString(p15.keyId()), - asn1obj.BitString([]byte{byte(0b00000010)}), - asn1obj.BitString([]byte{byte(0b01000000)}), - }), - - asn1obj.ExplicitCompound(1, [][]byte{ - asn1obj.Sequence([][]byte{ + // [1] Public Key + asn1obj.ExplicitCompound(1, [][]byte{ asn1obj.ExplicitCompound(0, [][]byte{ asn1obj.Sequence([][]byte{ + // commonObjectAttributes - Label asn1obj.Sequence([][]byte{ - asn1obj.ObjectIdentifier(asn1obj.OIDecPublicKey), - asn1obj.ObjectIdentifier(curveOID), + asn1obj.UTF8String(apcKeyLabel), + }), + // CommonKeyAttributes + asn1obj.Sequence([][]byte{ + asn1obj.OctetString(p15.keyId()), + asn1obj.BitString([]byte{byte(0b10000010)}), + asn1obj.BitString([]byte{byte(0b01000000)}), }), - asn1obj.BitString(ecdhKey.Bytes()), - }), - }), - }), - }), - }) - default: - // bad key type - return nil, errKeyWrongType - } - - // assemble complete object - key = - asn1obj.Sequence([][]byte{ - // contentType: OID: 1.2.840.113549.1.15.3.1 pkcs15content (PKCS #15 content type) - asn1obj.ObjectIdentifier(asn1obj.OIDPkscs15Content), - // content - asn1obj.ExplicitCompound(0, [][]byte{ - asn1obj.Sequence([][]byte{ - asn1obj.Integer(big.NewInt(0)), - asn1obj.Sequence([][]byte{ - // [0] Private Keys - asn1obj.ExplicitCompound(0, [][]byte{ - asn1obj.ExplicitCompound(0, [][]byte{ - privKeyObj, - }), - }), - // [1] Public Keys - asn1obj.ExplicitCompound(1, [][]byte{ - asn1obj.ExplicitCompound(0, [][]byte{ - pubKeyObj, + asn1obj.ExplicitCompound(1, [][]byte{ + asn1obj.Sequence([][]byte{ + asn1obj.ExplicitCompound(0, [][]byte{ + asn1obj.ExplicitCompound(1, [][]byte{ + asn1obj.Sequence([][]byte{ + asn1obj.ObjectIdentifier(asn1obj.OIDrsaEncryptionPKCS1), + asn1.NullBytes, + }), + // RSAPublicKey SubjectPublicKeyInfo + asn1obj.BitString( + asn1obj.Sequence([][]byte{ + asn1obj.Integer(p15.key.PublicKey.N), + asn1obj.Integer(big.NewInt(int64(p15.key.PublicKey.E))), + }), + ), + }), + }), + // not 100% certain but appears to be rsa key byte len + asn1obj.Integer(big.NewInt(int64(p15.key.PublicKey.N.BitLen() / 8))), + }), + }), }), }), }), }), }), - }) + }), + }) return key, nil } + +// ToP15File turns the key and cert into a properly formatted and encoded +// p15 file +func (p15 *pkcs15KeyCert) ToP15Files() (keyCertFile []byte, keyFile []byte, err error) { + // rsa encrypted key in encrypted envelope (will be shared by both output files) + envelope, err := p15.encryptedKeyEnvelope() + if err != nil { + return nil, nil, err + } + + // key + cert file + keyCertFile, err = p15.toP15KeyCert(envelope) + if err != nil { + return nil, nil, err + } + + // key only file + keyFile, err = p15.toP15Key(envelope) + if err != nil { + return nil, nil, err + } + + return keyCertFile, keyFile, nil +} diff --git a/pkg/pkcs15/private_key.go b/pkg/pkcs15/private_key.go index 59b538e..8c6b0fb 100644 --- a/pkg/pkcs15/private_key.go +++ b/pkg/pkcs15/private_key.go @@ -1,41 +1,24 @@ package pkcs15 -import ( - "apc-p15-tool/pkg/tools/asn1obj" - "crypto/ecdsa" - "crypto/rsa" -) +import "apc-p15-tool/pkg/tools/asn1obj" // privateKeyObject returns the ASN.1 representation of a private key func (p15 *pkcs15KeyCert) privateKeyObject() []byte { - var privKeyObj []byte + // ensure all expected vals are available + p15.key.Precompute() - switch privKey := p15.key.(type) { - case *rsa.PrivateKey: - privKey.Precompute() + pkey := asn1obj.Sequence([][]byte{ + // P + asn1obj.IntegerExplicitValue(3, p15.key.Primes[0]), + // Q + asn1obj.IntegerExplicitValue(4, p15.key.Primes[1]), + // Dp + asn1obj.IntegerExplicitValue(5, p15.key.Precomputed.Dp), + // Dq + asn1obj.IntegerExplicitValue(6, p15.key.Precomputed.Dq), + // Qinv + asn1obj.IntegerExplicitValue(7, p15.key.Precomputed.Qinv), + }) - // ensure all expected vals are available - privKeyObj = asn1obj.Sequence([][]byte{ - // P - asn1obj.IntegerExplicitValue(3, privKey.Primes[0]), - // Q - asn1obj.IntegerExplicitValue(4, privKey.Primes[1]), - // Dp - asn1obj.IntegerExplicitValue(5, privKey.Precomputed.Dp), - // Dq - asn1obj.IntegerExplicitValue(6, privKey.Precomputed.Dq), - // Qinv - asn1obj.IntegerExplicitValue(7, privKey.Precomputed.Qinv), - }) - - case *ecdsa.PrivateKey: - // Only private piece is the integer D - privKeyObj = asn1obj.Integer(privKey.D) - - default: - // panic if unsupported key - panic("private key type is unexpected and unsupported") - } - - return privKeyObj + return pkey } diff --git a/pkg/tools/asn1obj/oid.go b/pkg/tools/asn1obj/oid.go index 70009c2..3975ded 100644 --- a/pkg/tools/asn1obj/oid.go +++ b/pkg/tools/asn1obj/oid.go @@ -11,10 +11,6 @@ var ( OIDdesEDE3CBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 3, 7} // des-EDE3-CBC (RSADSI encryptionAlgorithm) OIDpkcs7Data = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1} // data (PKCS #7) OIDauthEnc128 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 3, 15} // authEnc128 (S/MIME Algorithms) - OIDecPublicKey = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} // ecPublicKey (ANSI X9.62 public key type) - OIDprime256v1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7} // prime256v1 (ANSI X9.62 named elliptic curve) - OIDsecp384r1 = asn1.ObjectIdentifier{1, 3, 132, 0, 34} // secp384r1 (SECG (Certicom) named elliptic curve) - OIDsecp521r1 = asn1.ObjectIdentifier{1, 3, 132, 0, 35} // secp521r1 (SECG (Certicom) named elliptic curve) ) // ObjectIdentifier returns an ASN.1 OBJECT IDENTIFIER with the oidValue bytes