mirror of
https://github.com/gregtwallace/apc-p15-tool.git
synced 2025-01-22 08:14:08 +00:00
app: restructure and start building p15 output
This commit is contained in:
parent
6610c92058
commit
e2e4f2037c
24 changed files with 622 additions and 168 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,4 +3,4 @@
|
||||||
*.pem
|
*.pem
|
||||||
|
|
||||||
# ignore test_data folder
|
# ignore test_data folder
|
||||||
/test_data
|
/_test_data
|
||||||
|
|
94
cek.go
94
cek.go
|
@ -1,94 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/des"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// decryptCEK decrypts the encrypted CEK and unwraps the CEK so only the
|
|
||||||
// original CEK is returned
|
|
||||||
func decryptCEK(encryptedCEK, encryptedCekSalt, KEK []byte) (CEK []byte, err error) {
|
|
||||||
// ensure proper var lens, or error
|
|
||||||
encryptedCEKLen := 24
|
|
||||||
CEKSaltLen := 8
|
|
||||||
KEKLen := 24
|
|
||||||
|
|
||||||
if len(encryptedCEK) != encryptedCEKLen {
|
|
||||||
return nil, errors.New("wrong encrypted CEK length")
|
|
||||||
}
|
|
||||||
if len(encryptedCekSalt) != CEKSaltLen {
|
|
||||||
return nil, errors.New("wrong encrypted CEK's salt length")
|
|
||||||
}
|
|
||||||
if len(KEK) != KEKLen {
|
|
||||||
return nil, errors.New("wrong KEK length")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3DES uses block byte size of 8
|
|
||||||
blockByteSize := 8
|
|
||||||
|
|
||||||
// make DES cipher from KEK
|
|
||||||
kekDesCipher, err := des.NewTripleDESCipher(KEK)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to make DES cipher for cek decryption (%s)", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// (1) first use n-1'th block as IV to decrypt n'th block
|
|
||||||
ivStart := encryptedCEKLen - 2*blockByteSize
|
|
||||||
ivEnd := encryptedCEKLen - 1*blockByteSize
|
|
||||||
|
|
||||||
ivBlockCipherText := encryptedCEK[ivStart:ivEnd]
|
|
||||||
nthBlockCipherText := encryptedCEK[encryptedCEKLen-1*blockByteSize:]
|
|
||||||
|
|
||||||
firstBlockDecrypter := cipher.NewCBCDecrypter(kekDesCipher, ivBlockCipherText)
|
|
||||||
|
|
||||||
decryptedNthBlock := make([]byte, len(nthBlockCipherText))
|
|
||||||
firstBlockDecrypter.CryptBlocks(decryptedNthBlock, nthBlockCipherText)
|
|
||||||
|
|
||||||
// (2) decrypt remainder of outer encryption blocks (1 ... n-1'th) using
|
|
||||||
// the decrypted nthBlock as the IV
|
|
||||||
outerRemainderDecrypter := cipher.NewCBCDecrypter(kekDesCipher, decryptedNthBlock)
|
|
||||||
|
|
||||||
decryptedOuterRemainder := make([]byte, encryptedCEKLen-1*blockByteSize)
|
|
||||||
outerRemainderDecrypter.CryptBlocks(decryptedOuterRemainder, encryptedCEK[:encryptedCEKLen-1*blockByteSize])
|
|
||||||
|
|
||||||
// combine decrypted remainder with decrypted nth block for complete decrypted bytes
|
|
||||||
// this is equivelant to having the outer encryption removed, AKA the CEK is encrypted
|
|
||||||
// once now instead of twice
|
|
||||||
onceEncryptedCEK := append(decryptedOuterRemainder, decryptedNthBlock...)
|
|
||||||
|
|
||||||
// (3) Decrypted the inner layer of encryption using the KEK (aka decrypt the remaining
|
|
||||||
// layer of encryption)
|
|
||||||
|
|
||||||
// inner decrypter uses original CEK salt
|
|
||||||
innerDecrypter := cipher.NewCBCDecrypter(kekDesCipher, encryptedCekSalt)
|
|
||||||
|
|
||||||
// once decrypted, the CEK is still formatted as:
|
|
||||||
// CEK byte count || check value || CEK || padding (if required)
|
|
||||||
formattedCEK := make([]byte, len(onceEncryptedCEK))
|
|
||||||
innerDecrypter.CryptBlocks(formattedCEK, onceEncryptedCEK)
|
|
||||||
|
|
||||||
// Now that CEK is decrypted, sanity check it
|
|
||||||
|
|
||||||
// first byte is CEK byte count
|
|
||||||
expectedCEKLen := formattedCEK[0]
|
|
||||||
|
|
||||||
// (1a) expected cek len must be 16 or 24 or 3DES (which is what APC uses)
|
|
||||||
if int(expectedCEKLen) != 16 && int(expectedCEKLen) != 24 {
|
|
||||||
return nil, errors.New("expected CEK len block size is %d but 3DES requires 16 or 24 (decrypting likely failed)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// next 3 bytes are the check value
|
|
||||||
CEKCheckVal := formattedCEK[1:4]
|
|
||||||
|
|
||||||
// CEK itself is the next bytes until CEK is the expected length
|
|
||||||
CEK = formattedCEK[4 : expectedCEKLen+4]
|
|
||||||
|
|
||||||
// (1b) key check data validation
|
|
||||||
if !isBitwiseCompliment(CEKCheckVal, CEK[0:3]) {
|
|
||||||
return nil, errors.New("CEK check value did not match CEK")
|
|
||||||
}
|
|
||||||
|
|
||||||
return CEK, nil
|
|
||||||
}
|
|
7
cmd/main.go
Normal file
7
cmd/main.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "apc-p15-tool/pkg/app"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app.Start()
|
||||||
|
}
|
20
go.mod
20
go.mod
|
@ -1,7 +1,21 @@
|
||||||
module temp
|
module apc-p15-tool
|
||||||
|
|
||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
require github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3
|
require (
|
||||||
|
github.com/peterbourgon/ff/v4 v4.0.0-alpha.4
|
||||||
|
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3
|
||||||
|
go.uber.org/zap v1.26.0
|
||||||
|
)
|
||||||
|
|
||||||
require golang.org/x/crypto v0.18.0
|
require go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
|
||||||
|
replace apc-p15-tool/cmd => /cmd
|
||||||
|
|
||||||
|
replace apc-p15-tool/pkg/app => /pkg/app
|
||||||
|
|
||||||
|
replace apc-p15-tool/pkg/pkcs15 => /pkg/pkcs15
|
||||||
|
|
||||||
|
replace apc-p15-tool/pkg/tools => /pkg/tools
|
||||||
|
|
||||||
|
replace apc-p15-tool/pkg/tools/asn1obj => /pkg/tools/asn1obj
|
||||||
|
|
22
go.sum
22
go.sum
|
@ -1,4 +1,22 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
|
||||||
|
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs=
|
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=
|
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA=
|
||||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||||
|
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
||||||
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||||
|
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
24
kek.go
24
kek.go
|
@ -1,24 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/pbkdf2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// makeKEK creates the APC KEK for a given Salt; APC uses a fixed
|
|
||||||
// password, iteration count, and hash function
|
|
||||||
func makeKEK(salt []byte) (KEK []byte) {
|
|
||||||
// password is known constant for APC files
|
|
||||||
password := "user"
|
|
||||||
|
|
||||||
// fixed values for APC files
|
|
||||||
iterations := 5000
|
|
||||||
hash := sha256.New
|
|
||||||
|
|
||||||
// size of 3DES key (k1 + k2 + k3)
|
|
||||||
size := 24
|
|
||||||
|
|
||||||
// kek
|
|
||||||
return pbkdf2.Key([]byte(password), salt, iterations, size, hash)
|
|
||||||
}
|
|
37
main.go
37
main.go
|
@ -1,37 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
p15Bytes, err := os.ReadFile("./apc9138a8cert-no-header.p15")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
apcHeader, err := makeFileHeader(p15Bytes)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
wizardBytes, err := os.ReadFile("./apc9138a.apc-wizard.p15")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
wizHeader := wizardBytes[:228]
|
|
||||||
|
|
||||||
log.Println(apcHeader)
|
|
||||||
log.Println(wizHeader)
|
|
||||||
|
|
||||||
for i := range wizHeader {
|
|
||||||
if apcHeader[i] != wizHeader[i] {
|
|
||||||
panic(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("match")
|
|
||||||
|
|
||||||
}
|
|
65
pkg/app/app.go
Normal file
65
pkg/app/app.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apc-p15-tool/pkg/pkcs15"
|
||||||
|
"encoding/base64"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// struct for receivers to use common app pieces
|
||||||
|
type app struct {
|
||||||
|
logger *zap.SugaredLogger
|
||||||
|
config *config
|
||||||
|
}
|
||||||
|
|
||||||
|
// actual application start
|
||||||
|
func Start() {
|
||||||
|
// make app w/ initial logger pre-config
|
||||||
|
initLogLevel := "debug"
|
||||||
|
app := &app{
|
||||||
|
logger: makeZapLogger(&initLogLevel),
|
||||||
|
}
|
||||||
|
|
||||||
|
// get config
|
||||||
|
app.getConfig()
|
||||||
|
|
||||||
|
// re-init logger with configured log level
|
||||||
|
app.logger = makeZapLogger(app.config.logLevel)
|
||||||
|
|
||||||
|
// break point for building additional alternate functions
|
||||||
|
|
||||||
|
// function: make p15 from pem files
|
||||||
|
|
||||||
|
// Read in PEM files
|
||||||
|
keyPem, err := os.ReadFile(*app.config.keyPemFilePath)
|
||||||
|
if err != nil {
|
||||||
|
app.logger.Fatalf("failed to read key file (%s)", err)
|
||||||
|
// FATAL
|
||||||
|
}
|
||||||
|
|
||||||
|
certPem, err := os.ReadFile(*app.config.certPemFilePath)
|
||||||
|
if err != nil {
|
||||||
|
app.logger.Fatalf("failed to read cert file (%s)", err)
|
||||||
|
// FATAL
|
||||||
|
}
|
||||||
|
|
||||||
|
p15, err := pkcs15.ParsePEMToPKCS15(keyPem, certPem)
|
||||||
|
if err != nil {
|
||||||
|
app.logger.Fatalf("failed to parse pem files (%s)", err)
|
||||||
|
// FATAL
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEMP TEMP TEMP
|
||||||
|
p15File, err := p15.ToP15File()
|
||||||
|
if err != nil {
|
||||||
|
app.logger.Fatalf("failed to make p15 file (%s)", err)
|
||||||
|
// FATAL
|
||||||
|
}
|
||||||
|
|
||||||
|
// app.logger.Debug(hex.EncodeToString(p15File))
|
||||||
|
app.logger.Debug(base64.RawStdEncoding.EncodeToString(p15File))
|
||||||
|
|
||||||
|
// TEMP TEMP TEMP
|
||||||
|
}
|
45
pkg/app/app_config.go
Normal file
45
pkg/app/app_config.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/peterbourgon/ff/v4"
|
||||||
|
"github.com/peterbourgon/ff/v4/ffhelp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
environmentVarPrefix = "APC_P15_TOOL"
|
||||||
|
)
|
||||||
|
|
||||||
|
// app's config options from user
|
||||||
|
type config struct {
|
||||||
|
logLevel *string
|
||||||
|
keyPemFilePath *string
|
||||||
|
certPemFilePath *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// getConfig returns the app's configuration from either command line args,
|
||||||
|
// or environment variables
|
||||||
|
func (app *app) getConfig() {
|
||||||
|
// make config and flag set
|
||||||
|
cfg := &config{}
|
||||||
|
fs := ff.NewFlagSet("apc-p15-tool")
|
||||||
|
|
||||||
|
// define options
|
||||||
|
cfg.logLevel = fs.StringEnum('l', "loglevel", "log level: debug, info, warn, error, dpanic, panic, or fatal",
|
||||||
|
"info", "debug", "warn", "error", "dpanic", "panic", "fatal")
|
||||||
|
|
||||||
|
cfg.keyPemFilePath = fs.StringLong("keyfile", "", "path and filename of the rsa-2048 key in pem format")
|
||||||
|
cfg.certPemFilePath = fs.StringLong("certfile", "", "path and filename of the rsa-2048 key in pem format")
|
||||||
|
// TODO key and pem directly in a flag/env var
|
||||||
|
|
||||||
|
// parse using args and/or ENV vars
|
||||||
|
err := ff.Parse(fs, os.Args[1:], ff.WithEnvVarPrefix(environmentVarPrefix))
|
||||||
|
if err != nil {
|
||||||
|
app.logger.Fatal(ffhelp.Flags(fs))
|
||||||
|
// FATAL
|
||||||
|
}
|
||||||
|
|
||||||
|
// set app config
|
||||||
|
app.config = cfg
|
||||||
|
}
|
47
pkg/app/app_logger.go
Normal file
47
pkg/app/app_logger.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// makeZapLogger creates a logger for the app; if log level is nil or does not parse
|
||||||
|
// the default 'Info' level will be used.
|
||||||
|
func makeZapLogger(logLevel *string) *zap.SugaredLogger {
|
||||||
|
// default info level
|
||||||
|
zapLevel := zapcore.InfoLevel
|
||||||
|
var parseErr error
|
||||||
|
|
||||||
|
// try to parse specified level (if there is one)
|
||||||
|
if logLevel != nil {
|
||||||
|
parseLevel, err := zapcore.ParseLevel(*logLevel)
|
||||||
|
if err != nil {
|
||||||
|
parseErr = err
|
||||||
|
} else {
|
||||||
|
zapLevel = parseLevel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make zap config
|
||||||
|
config := zap.NewProductionEncoderConfig()
|
||||||
|
config.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||||
|
config.LineEnding = "\n"
|
||||||
|
|
||||||
|
// no stack trace
|
||||||
|
config.StacktraceKey = ""
|
||||||
|
|
||||||
|
// make logger
|
||||||
|
consoleEncoder := zapcore.NewConsoleEncoder(config)
|
||||||
|
core := zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), zapLevel)
|
||||||
|
|
||||||
|
logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel)).Sugar()
|
||||||
|
|
||||||
|
// log deferred parse error if there was one
|
||||||
|
if logLevel != nil && parseErr != nil {
|
||||||
|
logger.Errorf("failed to parse requested log level \"%s\" (%s)", *logLevel, parseErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return logger
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
@ -24,7 +24,7 @@ func makeFileHeader(p15File []byte) ([]byte, error) {
|
||||||
// *(uint32_t *)(buf + 220) = (int32_t)calc_cksum(0, buf + 228, fileSize); // checksum of the original file
|
// *(uint32_t *)(buf + 220) = (int32_t)calc_cksum(0, buf + 228, fileSize); // checksum of the original file
|
||||||
// *(uint32_t *)(buf + 224) = (int32_t)calc_cksum(0, buf, 224); // checksum of the APC header
|
// *(uint32_t *)(buf + 224) = (int32_t)calc_cksum(0, buf, 224); // checksum of the APC header
|
||||||
|
|
||||||
// NOTE: This line is unused as it seems the APC tool code always writes this as a 1 for 2,048 bit
|
// NOTE: This line is unused as it seems the APC CLI tool v1.0.0 code always writes this as a 1 (regardless of key length)
|
||||||
// *(uint32_t *)(buf + 208) = keySize; // 1 for 1024 key, otherwise (2048 bit) 2
|
// *(uint32_t *)(buf + 208) = keySize; // 1 for 1024 key, otherwise (2048 bit) 2
|
||||||
// Unsure why this was in original code but seems irrelevant
|
// Unsure why this was in original code but seems irrelevant
|
||||||
|
|
35
pkg/pkcs15/keyid.go
Normal file
35
pkg/pkcs15/keyid.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package pkcs15
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apc-p15-tool/pkg/tools/asn1obj"
|
||||||
|
"crypto/sha1"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
// keyId returns the keyId for the overall key object
|
||||||
|
func (p15 *pkcs15KeyCert) keyId() []byte {
|
||||||
|
// Create Object to hash
|
||||||
|
hashObj := asn1obj.Sequence([][]byte{
|
||||||
|
asn1obj.Sequence([][]byte{
|
||||||
|
// Key is RSA
|
||||||
|
asn1obj.ObjectIdentifier(asn1obj.OIDrsaEncryptionPKCS1),
|
||||||
|
asn1obj.Null(),
|
||||||
|
}),
|
||||||
|
// 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(hashObj)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasher.Sum(nil)
|
||||||
|
}
|
66
pkg/pkcs15/marshal.go
Normal file
66
pkg/pkcs15/marshal.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package pkcs15
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apc-p15-tool/pkg/tools/asn1obj"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
apcKeyLabel = "Private key"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToP15File turns the key and cert into a properly formatted and encoded
|
||||||
|
// p15 file
|
||||||
|
func (p15 *pkcs15KeyCert) ToP15File() ([]byte, error) {
|
||||||
|
// private key object
|
||||||
|
pkey, err := p15.toP15PrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentInfo
|
||||||
|
p15File := asn1obj.Sequence([][]byte{
|
||||||
|
|
||||||
|
// contentType: OID: 1.2.840.113549.1.15.3.1 pkcs15content (PKCS #15 content type)
|
||||||
|
asn1obj.ObjectIdentifier(asn1obj.OIDPkscs15Content),
|
||||||
|
|
||||||
|
// content
|
||||||
|
asn1obj.Explicit(0,
|
||||||
|
asn1obj.Sequence([][]byte{
|
||||||
|
asn1obj.Integer(big.NewInt(0)),
|
||||||
|
asn1obj.Sequence([][]byte{
|
||||||
|
asn1obj.Explicit(0,
|
||||||
|
asn1obj.Explicit(0,
|
||||||
|
pkey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
return p15File, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toP15PrivateKey creates the encoded private key. it is broken our from the larger p15
|
||||||
|
// function for readability
|
||||||
|
func (p15 *pkcs15KeyCert) toP15PrivateKey() ([]byte, error) {
|
||||||
|
// key object
|
||||||
|
key := 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)}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
return key, nil
|
||||||
|
}
|
1
pkg/pkcs15/oids.go
Normal file
1
pkg/pkcs15/oids.go
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package pkcs15
|
120
pkg/pkcs15/pem_decode.go
Normal file
120
pkg/pkcs15/pem_decode.go
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
package pkcs15
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 (only pkcs1 and pkcs8 supported)")
|
||||||
|
errPemKeyWrongType = errors.New("pkcs15: pem key: unsupported key type (only rsa 2,048 supported)")
|
||||||
|
|
||||||
|
errPemCertBadBlock = errors.New("pkcs15: pem cert: failed to decode pem block")
|
||||||
|
errPemCertFailedToParse = errors.New("pkcs15: pem cert: failed to parse cert")
|
||||||
|
)
|
||||||
|
|
||||||
|
// pemKeyDecode attempts to decode a pem encoded byte slice and then attempts
|
||||||
|
// to parse an RSA private key from the decoded pem block. an error is returned
|
||||||
|
// if any of these steps fail OR if the rsa key is not of bitlen 2,048
|
||||||
|
func pemKeyDecode(keyPem []byte) (*rsa.PrivateKey, error) {
|
||||||
|
// decode
|
||||||
|
pemBlock, _ := pem.Decode([]byte(keyPem))
|
||||||
|
if pemBlock == nil {
|
||||||
|
return nil, errPemKeyBadBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsing depends on block type
|
||||||
|
var rsaKey *rsa.PrivateKey
|
||||||
|
|
||||||
|
switch pemBlock.Type {
|
||||||
|
case "RSA PRIVATE KEY": // PKCS1
|
||||||
|
var err error
|
||||||
|
|
||||||
|
rsaKey, err = x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errPemKeyFailedToParse
|
||||||
|
}
|
||||||
|
|
||||||
|
// basic sanity check
|
||||||
|
err = rsaKey.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("pkcs15: pem key: failed sanity check (%s)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify proper bitlen
|
||||||
|
if rsaKey.N.BitLen() != 2048 {
|
||||||
|
return nil, errPemKeyWrongType
|
||||||
|
}
|
||||||
|
|
||||||
|
// good to go
|
||||||
|
|
||||||
|
case "PRIVATE KEY": // PKCS8
|
||||||
|
pkcs8Key, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errPemKeyFailedToParse
|
||||||
|
}
|
||||||
|
|
||||||
|
switch pkcs8Key := pkcs8Key.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
rsaKey = pkcs8Key
|
||||||
|
|
||||||
|
// basic sanity check
|
||||||
|
err = rsaKey.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("pkcs15: pem key: failed sanity check (%s)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify proper bitlen
|
||||||
|
if rsaKey.N.BitLen() != 2048 {
|
||||||
|
return nil, errPemKeyWrongType
|
||||||
|
}
|
||||||
|
|
||||||
|
// good to go
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, errPemKeyWrongType
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, errPemKeyWrongBlockType
|
||||||
|
}
|
||||||
|
|
||||||
|
// if rsaKey is nil somehow, error
|
||||||
|
if rsaKey == nil {
|
||||||
|
return nil, errors.New("pkcs15: pem key: rsa key unexpectedly nil (report bug to project repo)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// success!
|
||||||
|
return rsaKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pemCertDecode attempts to decode a pem encoded byte slice and then attempts
|
||||||
|
// to parse a certificate from it. The certificate is also check against the
|
||||||
|
// key that is passed in to verify the key matches the certificate.
|
||||||
|
func pemCertDecode(certPem, keyPem []byte) (*x509.Certificate, error) {
|
||||||
|
// verify key and cert make a valid key pair
|
||||||
|
_, err := tls.X509KeyPair(certPem, keyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// discard rest, apc tool only bundles end cert
|
||||||
|
block, _ := pem.Decode(certPem)
|
||||||
|
if block == nil || block.Type != "CERTIFICATE" {
|
||||||
|
return nil, errPemCertBadBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the cert struct
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errPemCertFailedToParse
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, nil
|
||||||
|
}
|
36
pkg/pkcs15/pem_to_p15.go
Normal file
36
pkg/pkcs15/pem_to_p15.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package pkcs15
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pkcs15KeyCert holds the data for a key and certificate pair; it provides
|
||||||
|
// various methods to transform pkcs15 data
|
||||||
|
type pkcs15KeyCert struct {
|
||||||
|
key *rsa.PrivateKey
|
||||||
|
cert *x509.Certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePEMToPKCS15 parses the provide pem files to a pkcs15 struct; it also does some
|
||||||
|
// basic sanity check; if any of this fails, an error is returned
|
||||||
|
func ParsePEMToPKCS15(keyPem, certPem []byte) (*pkcs15KeyCert, error) {
|
||||||
|
// decode / check key
|
||||||
|
key, err := pemKeyDecode(keyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode / check cert
|
||||||
|
cert, err := pemCertDecode(certPem, keyPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p15 := &pkcs15KeyCert{
|
||||||
|
key: key,
|
||||||
|
cert: cert,
|
||||||
|
}
|
||||||
|
|
||||||
|
return p15, nil
|
||||||
|
}
|
27
pkg/tools/asn1obj/bitstring.go
Normal file
27
pkg/tools/asn1obj/bitstring.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package asn1obj
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/asn1"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BitString returns a BIT STRING of the content
|
||||||
|
func BitString(content []byte) []byte {
|
||||||
|
bs := asn1.BitString{
|
||||||
|
Bytes: content,
|
||||||
|
}
|
||||||
|
|
||||||
|
// drop trailing 0s by removing them from overall length
|
||||||
|
if len(content) > 0 {
|
||||||
|
trailing0s := bits.TrailingZeros8(content[len(content)-1])
|
||||||
|
bs.BitLength = 8*len(content) - trailing0s
|
||||||
|
}
|
||||||
|
|
||||||
|
// should never error
|
||||||
|
asn1result, err := asn1.Marshal(bs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return asn1result
|
||||||
|
}
|
17
pkg/tools/asn1obj/integer.go
Normal file
17
pkg/tools/asn1obj/integer.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package asn1obj
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/asn1"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Integer returns an ASN.1 OBJECT IDENTIFIER with the oidValue bytes
|
||||||
|
func Integer(bigInt *big.Int) []byte {
|
||||||
|
// should never error
|
||||||
|
asn1result, err := asn1.Marshal(bigInt)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return asn1result
|
||||||
|
}
|
27
pkg/tools/asn1obj/misc.go
Normal file
27
pkg/tools/asn1obj/misc.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package asn1obj
|
||||||
|
|
||||||
|
import "encoding/asn1"
|
||||||
|
|
||||||
|
// Explicit wraps another ASN.1 Object with the EXPLICIT wrapper using
|
||||||
|
// the tag number specified
|
||||||
|
func Explicit(explicitTagNumber int, wrappedElement []byte) []byte {
|
||||||
|
raw := asn1.RawValue{
|
||||||
|
Class: asn1.ClassContextSpecific,
|
||||||
|
Tag: explicitTagNumber,
|
||||||
|
IsCompound: true,
|
||||||
|
Bytes: wrappedElement,
|
||||||
|
}
|
||||||
|
|
||||||
|
// should never error
|
||||||
|
asn1result, err := asn1.Marshal(raw)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return asn1result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Null returns the NULL value
|
||||||
|
func Null() []byte {
|
||||||
|
return asn1.NullBytes
|
||||||
|
}
|
23
pkg/tools/asn1obj/octetstring.go
Normal file
23
pkg/tools/asn1obj/octetstring.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package asn1obj
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/asn1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OctetString returns an OCTET STRING of the content
|
||||||
|
func OctetString(content []byte) []byte {
|
||||||
|
raw := asn1.RawValue{
|
||||||
|
Class: asn1.ClassUniversal,
|
||||||
|
Tag: asn1.TagOctetString,
|
||||||
|
IsCompound: false,
|
||||||
|
Bytes: content,
|
||||||
|
}
|
||||||
|
|
||||||
|
// should never error
|
||||||
|
asn1result, err := asn1.Marshal(raw)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return asn1result
|
||||||
|
}
|
19
pkg/tools/asn1obj/oid.go
Normal file
19
pkg/tools/asn1obj/oid.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package asn1obj
|
||||||
|
|
||||||
|
import "encoding/asn1"
|
||||||
|
|
||||||
|
var (
|
||||||
|
OIDPkscs15Content = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 15, 3, 1} // pkcs15content (PKCS #15 content type)
|
||||||
|
OIDrsaEncryptionPKCS1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} // rsaEncryption (PKCS #1)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ObjectIdentifier returns an ASN.1 OBJECT IDENTIFIER with the oidValue bytes
|
||||||
|
func ObjectIdentifier(oid asn1.ObjectIdentifier) []byte {
|
||||||
|
// should never error
|
||||||
|
asn1result, err := asn1.Marshal(oid)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return asn1result
|
||||||
|
}
|
26
pkg/tools/asn1obj/sequence.go
Normal file
26
pkg/tools/asn1obj/sequence.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package asn1obj
|
||||||
|
|
||||||
|
import "encoding/asn1"
|
||||||
|
|
||||||
|
// Sequence returns an ASN.1 SEQUENCE with the specified content
|
||||||
|
func Sequence(content [][]byte) []byte {
|
||||||
|
val := []byte{}
|
||||||
|
for i := range content {
|
||||||
|
val = append(val, content[i]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := asn1.RawValue{
|
||||||
|
Class: asn1.ClassUniversal,
|
||||||
|
Tag: asn1.TagSequence,
|
||||||
|
IsCompound: true,
|
||||||
|
Bytes: val,
|
||||||
|
}
|
||||||
|
|
||||||
|
// should never error
|
||||||
|
asn1result, err := asn1.Marshal(raw)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return asn1result
|
||||||
|
}
|
16
pkg/tools/asn1obj/utf8string.go
Normal file
16
pkg/tools/asn1obj/utf8string.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package asn1obj
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/asn1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UTF8String returns the specified string as a UTF8String
|
||||||
|
func UTF8String(s string) []byte {
|
||||||
|
// should never error
|
||||||
|
asn1result, err := asn1.MarshalWithParams(s, "utf8")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return asn1result
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package main
|
package tools
|
||||||
|
|
||||||
// bitwiseComplimentOf returns the bitwise compliment of data
|
// BitwiseComplimentOf returns the bitwise compliment of data
|
||||||
func bitwiseComplimentOf(data []byte) []byte {
|
func BitwiseComplimentOf(data []byte) []byte {
|
||||||
compliment := []byte{}
|
compliment := []byte{}
|
||||||
|
|
||||||
for i := range data {
|
for i := range data {
|
||||||
|
@ -11,9 +11,9 @@ func bitwiseComplimentOf(data []byte) []byte {
|
||||||
return compliment
|
return compliment
|
||||||
}
|
}
|
||||||
|
|
||||||
// isBitwiseCompliment returns true if data1 and data2 are bitwise compliments,
|
// IsBitwiseCompliment returns true if data1 and data2 are bitwise compliments,
|
||||||
// otherwise it returns false
|
// otherwise it returns false
|
||||||
func isBitwiseCompliment(data1, data2 []byte) bool {
|
func IsBitwiseCompliment(data1, data2 []byte) bool {
|
||||||
// if not same length, definitely not compliments
|
// if not same length, definitely not compliments
|
||||||
if len(data1) != len(data2) {
|
if len(data1) != len(data2) {
|
||||||
return false
|
return false
|
Loading…
Reference in a new issue