Compare commits

...

15 commits
v1.2.2 ... main

Author SHA1 Message Date
Greg T. Wallace
9036299f4e v1.3.0 2025-06-23 20:02:38 -04:00
Greg T. Wallace
37b5d1871c remove stale comment 2025-06-23 20:01:41 -04:00
Greg T. Wallace
451fc36518 readme: update compatibility and troubleshooting 2025-06-23 19:55:15 -04:00
Greg T. Wallace
b821002e85 app: add compatibility warnings
Try to warn users in console output about possible certificate issues.

fixes: https://github.com/gregtwallace/apc-p15-tool/issues/20
2025-06-23 19:55:09 -04:00
Greg T. Wallace
7319fc16d8 pkcs15: minor lint 2025-06-23 19:54:59 -04:00
Greg T. Wallace
4a3f79ec07 v1.2.3 2025-06-19 23:05:02 -04:00
Greg T. Wallace
6537b88ffa build: fix subprocess args 2025-06-19 23:04:53 -04:00
Greg T. Wallace
6343cb0912 build action: use go.mod version 2025-06-19 22:58:47 -04:00
Greg T. Wallace
d869d7a2c9 readme: add build instructions 2025-06-19 22:22:36 -04:00
Greg T. Wallace
f5efcd7985 build: add freebsd amd64/arm64
fixes: https://github.com/gregtwallace/apc-p15-tool/issues/19
2025-06-19 22:22:23 -04:00
Greg T. Wallace
eb90d475cd dep: update all 2025-06-19 22:22:15 -04:00
Greg T. Wallace
1a71b9409e dep: go 1.24.4 2025-06-19 22:22:07 -04:00
Greg T. Wallace
a446351e5d build: make os agnostic
* rewrite build script in python
* switch action to use ubuntu
2025-06-19 22:21:57 -04:00
Greg T. Wallace
7c3ae2d16e build: simplify build process 2025-06-19 22:21:48 -04:00
Greg T. Wallace
c9ab6ae050
Update README.md 2025-05-31 22:59:50 -04:00
14 changed files with 352 additions and 544 deletions

View file

@ -8,83 +8,9 @@ on:
env:
GITHUB_REF: ${{ github.ref }}
GO_VERSION: '1.24.2'
jobs:
build-common:
runs-on: ubuntu-24.04
steps:
- name: Checkout Main Repo
uses: actions/checkout@v4
with:
repository: gregtwallace/apc-p15-tool
ref: ${{ env.GITHUB_REF }}
fetch-depth: 0
- name: Save README
uses: actions/upload-artifact@v4
with:
name: README.md
path: ./README.md
- name: Save LICENSE
uses: actions/upload-artifact@v4
with:
name: LICENSE.md
path: ./LICENSE.md
- name: Save CHANGELOG
uses: actions/upload-artifact@v4
with:
name: CHANGELOG.md
path: ./CHANGELOG.md
###
build-linux-arm64:
runs-on: ubuntu-24.04-arm
steps:
- name: Checkout 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: linux
GOARCH: arm64
CGO_ENABLED: 0
- name: Save Compiled Binary
uses: actions/upload-artifact@v4
with:
name: apc-p15-tool-linux-arm64
path: ./apc-p15-tool
- name: Build Install Only
run: go build -o ./apc-p15-install -v ./cmd/install_only
env:
GOOS: linux
GOARCH: arm64
CC: aarch64-linux-gnu-gcc
CGO_ENABLED: 0
- name: Save Compiled Binary
uses: actions/upload-artifact@v4
with:
name: apc-p15-install-linux-arm64
path: ./apc-p15-install
build-linux-amd64:
build-all:
runs-on: ubuntu-24.04
steps:
- name: Checkout Backend Repo
@ -97,375 +23,14 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '${{ env.GO_VERSION }}'
go-version-file: 'go.mod'
- name: Build Tool
run: go build -o ./apc-p15-tool -v ./cmd/tool
env:
GOOS: linux
GOARCH: amd64
CGO_ENABLED: 0
- name: Build All
run: |
python ./build_release.py
- name: Save Compiled Binary
- name: Save Zip of all targets
uses: actions/upload-artifact@v4
with:
name: apc-p15-tool-linux-amd64
path: ./apc-p15-tool
- name: Build Install Only
run: go build -o ./apc-p15-install -v ./cmd/install_only
env:
GOOS: linux
GOARCH: amd64
CGO_ENABLED: 0
- name: Save Compiled Binary
uses: actions/upload-artifact@v4
with:
name: apc-p15-install-linux-amd64
path: ./apc-p15-install
build-windows-amd64:
runs-on: windows-latest
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.exe -v ./cmd/tool
env:
GOOS: windows
GOARCH: amd64
CGO_ENABLED: 0
- name: Save Compiled Binary
uses: actions/upload-artifact@v4
with:
name: apc-p15-tool-windows-amd64
path: ./apc-p15-tool.exe
- name: Build Install Only
run: go build -o ./apc-p15-install.exe -v ./cmd/install_only
env:
GOOS: windows
GOARCH: amd64
CGO_ENABLED: 0
- name: Save Compiled Binary
uses: actions/upload-artifact@v4
with:
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
steps:
- name: Make release directory
run: mkdir ./release
- name: Download Tool Binary
uses: actions/download-artifact@v4
with:
name: apc-p15-tool-linux-arm64
path: ./release
- name: Download Install Binary
uses: actions/download-artifact@v4
with:
name: apc-p15-install-linux-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_linux_arm64
path: ./release
release-file-linux-amd64:
needs: [build-common, build-linux-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-linux-amd64
path: ./release
- name: Download Install Binary
uses: actions/download-artifact@v4
with:
name: apc-p15-install-linux-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_linux_amd64
path: ./release
release-file-windows-amd64:
needs: [build-common, build-windows-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-windows-amd64
path: ./release
- name: Download Install Binary
uses: actions/download-artifact@v4
with:
name: apc-p15-install-windows-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_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
name: apc-p15-tool-release
path: ./_out/_release

View file

@ -1,5 +1,34 @@
# APC P15 Tool Changelog
## [v1.3.0] - 2025-06-23
This release attempts to detect and warn of possible incompatibilies with a
spcecified certificate. NMCs do not warn or error when a bad file is installed,
instead they silently fail and generally just generate a new self-signed
certificate. This release checks some properties of the specified certificate
and produces warning messages that can be referenced if the cert installation
appears to work but ultimately doesn't prododuce the expected result.
- Add warnings based on key type, signature algorithm, validity dates, and
extensions.
- Minor lint.
## [v1.2.3] - 2025-06-19
Minor updates to the application. Large updates to the build process to
improve building, releasing, and maintainability.
- Go updated to 1.24.4 and all dependencies updated.
- Added FreeBSD arm64 and amd64 builds.
- Build process overhauled for simplicity. Build is now OS agnostic. PowerShell
script was removed and replaced with a python script.
- Build instructions added to README.
- GitHub build action now only runs in one Ubuntu container and cross-compiles.
- Release windows and macos as zip files and all others as gztar.
- Add file permissions for non-windows and non-macos releases.
## [v1.2.2] - 2025-04-22
All dependencies updated.

View file

@ -53,13 +53,14 @@ This project aims to solve all of these problems by accepting the most
common key and cert file format (PEM) and by being 100% open source
and licensed under the GPL-3.0 license.
## Compatibility Notice
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
Ensure you select an appropriate key!
NMC2 is extremely picky about the key type and size it supports. NMC3 is a bit
more flexible. Beware, some ACME clients will generate an ECDSA key by default
which is NOT supported by NMC2.
NMC2:
- RSA 1,024, 2,048, 3,072* bit lengths.
@ -80,22 +81,60 @@ NMC3*:
this size if possible. Most (all?) public ACME services won't accept keys
of this size anyway.
### General Troubleshooting
### Compatibility Notice
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.
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
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.
Generally, if there is a compatibility issue, there is a good chance you will
not see an error. Rather, the NMC will silently fail and you'll only know
something went wrong because the NMC's certificate didn't update, or it regenerated
a self-signed certificate that you'll see upon your next connection attempt.
I've tried to add some `WARNING` messages to the tool to indicate what might
be going wrong, but the list is definitely not exhaustive.
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.
### Troubleshooting
In particular, if you are experiencing `ssh: handshake failed:` first try
Suggested troubleshooting steps:
- Review the `Key Types and Sizes` and `Compatibility Notice` sections of this
README.
- Update your NMC's firmware to the latest version.
- Read this tool's output, look specifically for any `WARNING` messages and
adjust your certificate accordingly.
- Test using an RSA 2048 bit key to obtain a certificate from Let's Encrypt.
Their certificates are known to work with NMC.
- Use the official NMC Security Wizard to verify you can create a working
certificate and load it into your NMC. If the official tool does not work
switching to this tool won't help.
If you have tried all of these steps and are still experiencing a problem,
you may open an Issue on GitHub.
Include:
- The full command you are running that is causing the problem.
- The full log of this tool's output when you run the command. Append the
`--debug` flag to your command to get the debug output.
Keep in mind, I am one person with one specific hardware setup. I may not
be able to help you.
#### NMC3 Install `ssh: parse error in message type 53` Error
Configuring a `System Message` on an NMC3 breaks the install function. I do
not have an NMC3 and after doing some code review it is highly unlikely I'll
be able to fix this. Don't use a `System Message` if the install feature is
important to you.
see: https://github.com/gregtwallace/apc-p15-tool/issues/14
#### Install `ssh: handshake failed` Error
If you are experiencing `ssh: handshake failed:` first try
using the `--insecurecipher` flag. If this works, you should upgrade your
NMC to a newer firmware which includes secure ciphers. You should NOT automate
your environment using this flag as SSH over these ciphers is broken and
@ -171,6 +210,20 @@ separate script.
![Cert Warden with APC P15 Tool](https://raw.githubusercontent.com/gregtwallace/apc-p15-tool/main/img/apc-p15-tool.png)
## Building
Python3, Go, and git all must be installed to run the build script.
Once the dependencies are installed, clone this repo and run
`python build_release.py`. If you only want to build for certain OS or
ARCH targets, edit the `targets` array in the `build_release.py` file
before running it.
## 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

View file

@ -1,58 +0,0 @@
# Parent dir is root
$scriptDir = Get-Location
$outDir = Join-Path -Path $scriptDir -ChildPath "/_out"
# Windows x64
$env:GOARCH = "amd64"
$env:GOOS = "windows"
$env:CGO_ENABLED = 0
go build -o $outDir/apc-p15-tool-amd64.exe ./cmd/tool
$env:GOARCH = "amd64"
$env:GOOS = "windows"
$env:CGO_ENABLED = 0
go build -o $outDir/apc-p15-install-amd64.exe ./cmd/install_only
# Linux x64
$env:GOARCH = "amd64"
$env:GOOS = "linux"
$env:CGO_ENABLED = 0
go build -o $outDir/apc-p15-tool-amd64 ./cmd/tool
$env:GOARCH = "amd64"
$env:GOOS = "linux"
$env:CGO_ENABLED = 0
go build -o $outDir/apc-p15-install-amd64 ./cmd/install_only
# Linux arm64
$env:GOARCH = "arm64"
$env:GOOS = "linux"
$env:CGO_ENABLED = 0
go build -o $outDir/apc-p15-tool-arm64 ./cmd/tool
$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

89
build_release.py Normal file
View file

@ -0,0 +1,89 @@
#!/usr/bin/env python3
import os.path
import shutil
import subprocess
import tarfile
# Configuration
# output path (relative to this script)
outRelativeDir = "_out"
# target strings must be in the format:
# `GOOS_GOARCH`
# see: https://github.com/golang/go/blob/master/src/internal/syslist/syslist.go
# or unofficially: https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63
targets = [
"windows_amd64",
"linux_amd64",
"linux_arm64",
"darwin_amd64",
"darwin_arm64",
"freebsd_amd64",
"freebsd_arm64",
]
###
# Script
# relative dir is root
scriptDir = dirname = os.path.dirname(__file__)
outBaseDir = os.path.join(scriptDir, outRelativeDir)
releaseDir = os.path.join(outBaseDir, "_release")
# recreate paths
if os.path.exists(outBaseDir):
shutil.rmtree(outBaseDir)
os.makedirs(outBaseDir)
os.makedirs(releaseDir)
# get version number / tag
gitTag = subprocess.check_output(["git", "describe", "--tags", "--abbrev=0"]).decode('utf-8').strip()
# loop through and build all targets
for target in targets:
# environment vars
split = target.split("_")
GOOS = split[0]
GOARCH = split[1]
os.environ["GOOS"] = GOOS
os.environ["GOARCH"] = GOARCH
os.environ["CGO_ENABLED"] = "0"
# send build product to GOOS_GOARCH subfolders
targetOutDir = os.path.join(outBaseDir, target)
if not os.path.exists(targetOutDir):
os.makedirs(targetOutDir)
# special case for windows to add file extensions
extension = ""
if GOOS.lower() == "windows":
extension = ".exe"
# build binary and install only binary
subprocess.run(["go", "build", "-o", f"{targetOutDir}/apc-p15-tool{extension}", "./cmd/tool"])
subprocess.run(["go", "build", "-o", f"{targetOutDir}/apc-p15-install{extension}", "./cmd/install_only"])
# copy other important files for release
shutil.copy("README.md", targetOutDir)
shutil.copy("CHANGELOG.md", targetOutDir)
shutil.copy("LICENSE.md", targetOutDir)
# compress release file
# special case for windows & mac to use zip format
if GOOS.lower() == "windows" or GOOS.lower() == "darwin":
shutil.make_archive(f"{releaseDir}/apc-p15-tool-{gitTag}_{target}", "zip", targetOutDir)
else:
# for others, use gztar and set permissions on the files
# filter for setting permissions
def set_permissions(tarinfo):
if tarinfo.name == "apc-p15-tool" or tarinfo.name == "apc-p15-install":
tarinfo.mode = 0o0755
else:
tarinfo.mode = 0o0644
return tarinfo
# make tar
with tarfile.open(f"{releaseDir}/apc-p15-tool-{gitTag}_{target}.tar.gz", "w:gz") as tar:
for file in os.listdir(targetOutDir):
tar.add(os.path.join(targetOutDir, file), arcname=file, recursive=False, filter=set_permissions)

6
go.mod
View file

@ -1,14 +1,14 @@
module apc-p15-tool
go 1.24.2
go 1.24.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
golang.org/x/crypto v0.39.0
)
require golang.org/x/sys v0.32.0 // indirect
require golang.org/x/sys v0.33.0 // indirect
replace apc-p15-tool/cmd/install_only => /cmd/install_only

12
go.sum
View file

@ -4,11 +4,11 @@ github.com/peterbourgon/ff/v4 v4.0.0-alpha.4 h1:aiqS8aBlF9PsAKeMddMSfbwp3smONCn3
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=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View file

@ -12,7 +12,7 @@ import (
)
const (
appVersion = "1.2.2"
appVersion = "1.3.0"
)
// struct for receivers to use common app pieces

View file

@ -145,7 +145,7 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error {
// 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")
return errors.New("install: web ui leaf cert does not match new cert (your cert may not be compatible with NMC; check for WARNINGs in this tool's output)")
}
app.stdLogger.Println("install: ups web ui cert verified")

View file

@ -2,8 +2,11 @@ package app
import (
"apc-p15-tool/pkg/pkcs15"
"crypto/x509"
"encoding/asn1"
"fmt"
"slices"
"time"
)
// list of keys supported by the NMC2
@ -13,6 +16,32 @@ var nmc2SupportedKeyTypes = []pkcs15.KeyType{
pkcs15.KeyTypeRSA3072, // officially not supported but works
}
// known good signing algorithms
var knownSupportedNMC2SigningAlgs = []x509.SignatureAlgorithm{
x509.SHA256WithRSA,
}
var knownSupportedNMC3SigningAlgs = append(knownSupportedNMC2SigningAlgs, []x509.SignatureAlgorithm{
x509.ECDSAWithSHA384,
}...)
// known supported cert extensions
var knownSupportedCriticalOIDs = []asn1.ObjectIdentifier{
{2, 5, 29, 15}, // keyUsage
{2, 5, 29, 19}, // basicConstraints
{2, 5, 29, 17}, // subjectAltName
}
var knownSupportedOIDs = append(knownSupportedCriticalOIDs, []asn1.ObjectIdentifier{
{2, 5, 29, 37}, // extKeyUsage
{2, 5, 29, 14}, // subjectKeyIdentifier
{2, 5, 29, 35}, // authorityKeyIdentifier
{1, 3, 6, 1, 5, 5, 7, 1, 1}, // authorityInfoAccess
{2, 5, 29, 32}, // certificatePolicies
{1, 3, 6, 1, 4, 1, 11129, 2, 4, 2}, // googleSignedCertificateTimestamp
{2, 5, 29, 31}, // cRLDistributionPoints
}...)
// 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
@ -37,7 +66,10 @@ func (app *app) pemToAPCP15(keyPem, certPem []byte, parentCmdName string) (keyFi
app.stdLogger.Printf("%s: successfully generated p15 key file content", parentCmdName)
// check key type for compat with NMC2
nmc2KeyType := false
if slices.Contains(nmc2SupportedKeyTypes, p15.KeyType()) {
nmc2KeyType = true
app.stdLogger.Printf("%s: key type is supported by NMC2, generating p15 key+cert file content...", parentCmdName)
// make file bytes
@ -54,11 +86,84 @@ func (app *app) pemToAPCP15(keyPem, certPem []byte, parentCmdName string) (keyFi
// 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)
}
// check various parts of cert and log compatibility warnings
warned := false
// key not supported for NMC2
if !nmc2KeyType {
app.stdLogger.Printf("WARNING: %s: key type is %s and is not supported by NMC2.", parentCmdName, p15.KeyType().String())
warned = true
}
// signature algorithm (see: https://github.com/gregtwallace/apc-p15-tool/issues/18)
if !nmc2KeyType {
// definitely not for NMC2
if !slices.Contains(knownSupportedNMC3SigningAlgs, p15.Cert.SignatureAlgorithm) {
app.stdLogger.Printf("WARNING: %s: Certificate signing algorithm is %s and it is not known if NMC3 supports this algorithm.", parentCmdName, p15.Cert.SignatureAlgorithm.String())
warned = true
}
} else {
// could be for either NMC2 or NMC3
if !slices.Contains(knownSupportedNMC2SigningAlgs, p15.Cert.SignatureAlgorithm) {
if !slices.Contains(knownSupportedNMC3SigningAlgs, p15.Cert.SignatureAlgorithm) {
// not in NMC2 or NMC3 list
app.stdLogger.Printf("WARNING: %s: Certificate signing algorithm is %s and is not supported by NMC2. It is also not known if NMC3 supports this algorithm.", parentCmdName, p15.Cert.SignatureAlgorithm.String())
} else {
// not in NMC2 list, but is in NMC3 list
app.stdLogger.Printf("WARNING: %s: Certificate signing algorithm is %s and it does not support NMC2.", parentCmdName, p15.Cert.SignatureAlgorithm.String())
}
warned = true
}
}
// check validity dates
if time.Now().Before(p15.Cert.NotBefore) {
app.stdLogger.Printf("WARNING: %s: Current time (%s) is before certificate's NotBefore time (%s).",
parentCmdName, time.Now().Format(timeLoggingFormat), p15.Cert.NotBefore.Format(timeLoggingFormat))
warned = true
}
if time.Now().After(p15.Cert.NotAfter) {
app.stdLogger.Printf("WARNING: %s: Current time (%s) is after certificate's NotAfter time (%s).",
parentCmdName, time.Now().Format(timeLoggingFormat), p15.Cert.NotAfter.Format(timeLoggingFormat))
warned = true
}
// check extensions against known working extensions
for _, extension := range p15.Cert.Extensions {
// critical or not?
okOIDs := knownSupportedCriticalOIDs
criticalLogMsg := "Critical "
if !extension.Critical {
okOIDs = knownSupportedOIDs
criticalLogMsg = ""
}
// validate OIDs
ok := false
for _, okOID := range okOIDs {
if okOID.Equal(extension.Id) {
ok = true
break
}
}
if !ok {
app.stdLogger.Printf("WARNING: %s: %sExtension %s may not be supported by NMC.", parentCmdName, criticalLogMsg, extension.Id.String())
}
}
// log a message about possible failure
if warned {
app.stdLogger.Printf("WARNING: %s: Possible certificate compatibility issues were detected. If the resulting p15 file "+
"does not work with your NMC (e.g., a self-signed certificate is regenerated after you try to install the p15), "+
"modify your certificate to resolve the warnings and try again.", parentCmdName)
}
// end compatibility warnings
app.stdLogger.Printf("%s: apc p15 file(s) data succesfully generated", parentCmdName)
return keyFile, apcKeyCertFile, nil

View file

@ -25,7 +25,7 @@ const (
// 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 {
if len(p15.envelopedPrivateKey) > 0 {
return nil
}

View file

@ -15,7 +15,7 @@ func (p15 *pkcs15KeyCert) keyId() []byte {
// SHA-1 Hash
hasher := sha1.New()
_, err := hasher.Write(p15.cert.RawSubjectPublicKeyInfo)
_, err := hasher.Write(p15.Cert.RawSubjectPublicKeyInfo)
if err != nil {
panic(err)
}
@ -46,9 +46,9 @@ func (p15 *pkcs15KeyCert) keyIdInt3() []byte {
// object to hash
hashObj := asn1obj.Sequence([][]byte{
// issuerDistinguishedName
p15.cert.RawIssuer,
p15.Cert.RawIssuer,
// serialNumber
asn1obj.Integer(p15.cert.SerialNumber),
asn1obj.Integer(p15.Cert.SerialNumber),
})
// SHA-1 Hash
@ -74,7 +74,7 @@ func (p15 *pkcs15KeyCert) keyIdInt6() []byte {
// SHA-1 Hash
hasher := sha1.New()
_, err := hasher.Write(p15.cert.RawIssuer)
_, err := hasher.Write(p15.Cert.RawIssuer)
if err != nil {
panic(err)
}
@ -95,7 +95,7 @@ func (p15 *pkcs15KeyCert) keyIdInt7() []byte {
// SHA-1 Hash
hasher := sha1.New()
_, err := hasher.Write(p15.cert.RawSubject)
_, err := hasher.Write(p15.Cert.RawSubject)
if err != nil {
panic(err)
}
@ -168,7 +168,7 @@ func (p15 *pkcs15KeyCert) keyIdInt9() []byte {
// 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()))
binary.BigEndian.PutUint32(time, uint32(p15.Cert.NotBefore.Unix()))
publicKeyPacket = append(publicKeyPacket, time...)
// the next part is key type specific

View file

@ -10,8 +10,8 @@ import (
// pkcs15KeyCert holds the data for a key and certificate pair; it provides
// various methods to transform pkcs15 data
type pkcs15KeyCert struct {
Cert *x509.Certificate
key crypto.PrivateKey
cert *x509.Certificate
// store the encrypted enveloped Private Key for re-use
envelopedPrivateKey []byte
}
@ -32,6 +32,31 @@ const (
KeyTypeUnknown
)
// String returns the private key type in a log friendly string format.
func (keyType KeyType) String() string {
switch keyType {
case KeyTypeRSA1024:
return "RSA 1024-bit"
case KeyTypeRSA2048:
return "RSA 2048-bit"
case KeyTypeRSA3072:
return "RSA 3072-bit"
case KeyTypeRSA4096:
return "RSA 4096-bit"
case KeyTypeECP256:
return "ECDSA P-256"
case KeyTypeECP384:
return "ECDSA P-384"
case KeyTypeECP521:
return "ECDSA P-521"
default:
}
return "unknown key type"
}
// KeyType returns the private key type
func (p15 *pkcs15KeyCert) KeyType() KeyType {
switch pKey := p15.key.(type) {
@ -85,7 +110,7 @@ func ParsePEMToPKCS15(keyPem, certPem []byte) (*pkcs15KeyCert, error) {
// create p15 struct
p15 := &pkcs15KeyCert{
key: key,
cert: cert,
Cert: cert,
}
// pre-calculate encrypted envelope

View file

@ -43,9 +43,9 @@ func (p15 *pkcs15KeyCert) ToP15KeyCert() (keyCert []byte, err error) {
// CommonKeyAttributes - accessFlags (trailing 0s will drop)
asn1obj.BitString([]byte{byte(0b10110000)}),
// CommonKeyAttributes - startDate
asn1obj.GeneralizedTime(p15.cert.NotBefore),
asn1obj.GeneralizedTime(p15.Cert.NotBefore),
// CommonKeyAttributes - [0] endDate
asn1obj.GeneralizedTimeExplicitValue(0, p15.cert.NotAfter),
asn1obj.GeneralizedTimeExplicitValue(0, p15.Cert.NotAfter),
}),
// ObjectValue - indirect-protected
asn1obj.ExplicitCompound(1, [][]byte{
@ -74,9 +74,9 @@ func (p15 *pkcs15KeyCert) ToP15KeyCert() (keyCert []byte, err error) {
// CommonKeyAttributes - accessFlags (trailing 0s will drop)
asn1obj.BitString([]byte{byte(0b10110000)}),
// CommonKeyAttributes - startDate
asn1obj.GeneralizedTime(p15.cert.NotBefore),
asn1obj.GeneralizedTime(p15.Cert.NotBefore),
// CommonKeyAttributes - [0] endDate
asn1obj.GeneralizedTimeExplicitValue(0, p15.cert.NotAfter),
asn1obj.GeneralizedTimeExplicitValue(0, p15.Cert.NotAfter),
}),
// ObjectValue - indirect-protected
asn1obj.ExplicitCompound(1, [][]byte{
@ -114,15 +114,15 @@ func (p15 *pkcs15KeyCert) ToP15KeyCert() (keyCert []byte, err error) {
p15.keyIdInt9(),
}),
// CommonKeyAttributes - startDate
asn1obj.GeneralizedTime(p15.cert.NotBefore),
asn1obj.GeneralizedTime(p15.Cert.NotBefore),
// CommonKeyAttributes - [4] endDate
asn1obj.GeneralizedTimeExplicitValue(4, p15.cert.NotAfter),
asn1obj.GeneralizedTimeExplicitValue(4, p15.Cert.NotAfter),
}),
// actual certificate itself
asn1obj.ExplicitCompound(1, [][]byte{
asn1obj.Sequence([][]byte{
asn1obj.ExplicitCompound(0, [][]byte{
p15.cert.Raw,
p15.Cert.Raw,
}),
}),
}),