key: finish key encoding and start cert

This commit is contained in:
Greg T. Wallace 2024-01-27 11:35:35 -05:00
parent 85462c93b1
commit 1f6dad4907
14 changed files with 592 additions and 115 deletions

View file

@ -0,0 +1,219 @@
package pkcs15
import (
"apc-p15-tool/pkg/tools"
"apc-p15-tool/pkg/tools/asn1obj"
"crypto/cipher"
"crypto/des"
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"encoding/asn1"
"math/big"
"golang.org/x/crypto/pbkdf2"
)
// fixed specs for apc cert
const (
apcKEKPassword = "user"
apcKEKIterations = 5000
)
// 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 nil, err
}
// kek hash alg
kekHash := sha256.New
// size of 3DES key (k1 + k2 + k3)
kekSize := 24
// kek
kek := pbkdf2.Key([]byte(apcKEKPassword), kekSalt, apcKEKIterations, kekSize, kekHash)
// make DES cipher from KEK for CEK
cekDesCipher, err := des.NewTripleDESCipher(kek)
if err != nil {
return nil, err
}
// cek (16 bytes for authEnc128) -- see: rfc3211
cekLen := uint8(16)
cek := make([]byte, cekLen)
_, err = rand.Read(cek)
if err != nil {
return nil, err
}
// LEN + Check Val [3]
wrappedCEK := append([]byte{cekLen}, tools.BitwiseComplimentOf(cek[:3])...)
// + CEK
wrappedCEK = append(wrappedCEK, cek...)
// + padding (if needed)
// pad wrapped CEK to min 2 * block len
cekPadLen := 0
if len(wrappedCEK) < 2*cekDesCipher.BlockSize() {
cekPadLen = 2*cekDesCipher.BlockSize() - len(wrappedCEK)
} else if len(wrappedCEK)%cekDesCipher.BlockSize() != 0 {
// pad if not a multiple of block len
cekPadLen = cekDesCipher.BlockSize() - len(wrappedCEK)%cekDesCipher.BlockSize()
}
cekPadding := make([]byte, cekPadLen)
_, err = rand.Read(cekPadding)
if err != nil {
return nil, err
}
wrappedCEK = append(wrappedCEK, cekPadding...)
// double encrypt CEK
cekEncryptSalt := make([]byte, 8)
_, err = rand.Read(cekEncryptSalt)
if err != nil {
return nil, err
}
cekEncrypter := cipher.NewCBCEncrypter(cekDesCipher, cekEncryptSalt)
encryptedCEKOnly1Rd := make([]byte, len(wrappedCEK))
cekEncrypter.CryptBlocks(encryptedCEKOnly1Rd, wrappedCEK)
encryptedCEK := make([]byte, len(encryptedCEKOnly1Rd))
cekEncrypter.CryptBlocks(encryptedCEK, encryptedCEKOnly1Rd)
// content encryption
contentEncSalt := make([]byte, 8)
_, err = rand.Read(contentEncSalt)
if err != nil {
return nil, err
}
contentEncryptKey := pbkdf2.Key(cek, []byte("encryption"), 1, 24, sha1.New)
contentDesCipher, err := des.NewTripleDESCipher(contentEncryptKey)
if err != nil {
return nil, err
}
content := p15.privateKeyObject()
// pad content, see: https://datatracker.ietf.org/doc/html/rfc3852 6.3
contentPadLen := uint8(contentDesCipher.BlockSize() - (len(content) % contentDesCipher.BlockSize()))
// ALWAYS pad, if content is exact, add full block of padding
if contentPadLen == 0 {
contentPadLen = uint8(contentDesCipher.BlockSize())
}
for i := uint8(1); i <= contentPadLen; i++ {
content = append(content, byte(contentPadLen))
}
contentEncrypter := cipher.NewCBCEncrypter(contentDesCipher, contentEncSalt)
encryptedContent := make([]byte, len(content))
contentEncrypter.CryptBlocks(encryptedContent, content)
// encrypted content MAC
macKey := pbkdf2.Key(cek, []byte("authentication"), 1, 32, sha1.New)
// data encryption alg block
encAlgObj := asn1obj.Sequence([][]byte{
// ContentEncryptionAlgorithmIdentifier
asn1obj.ObjectIdentifier(asn1obj.OIDauthEnc128),
// ContentEncryptionAlgorithmIdentifier details/info
asn1obj.Sequence([][]byte{
// encryption alg & salt
asn1obj.Sequence([][]byte{
// encryption alg
asn1obj.ObjectIdentifier(asn1obj.OIDdesEDE3CBC),
// encryption alg's salt
asn1obj.OctetString(contentEncSalt),
}),
// mac alg
asn1obj.Sequence([][]byte{
asn1obj.ObjectIdentifier(asn1obj.OIDhmacWithSHA256),
asn1.NullBytes,
}),
}),
})
macHasher := hmac.New(sha256.New, macKey)
// the data the MAC covers is the algId header bytes + encrypted data bytes
hashMe := append(encAlgObj, encryptedContent...)
// make MAC
_, err = macHasher.Write(hashMe)
if err != nil {
return nil, err
}
mac := macHasher.Sum(nil)
// build object
// AuthEnvelopedData Type
envelope := [][]byte{
// CMSVersion
asn1obj.Integer(big.NewInt(2)),
// RecipientInfos
asn1obj.Set([][]byte{
// 1st and only 'RecipientInfo' - pwri [3] PasswordRecipientinfo
asn1obj.ExplicitCompound(3, [][]byte{
// CMSVersion
asn1obj.Integer(big.NewInt(0)),
// keyDerivationAlgorithm [0]
asn1obj.ExplicitCompound(0, [][]byte{
// KeyDerivationAlgorithmIdentifier
asn1obj.ObjectIdentifier(asn1obj.OIDpkcs5PBKDF2),
// KeyDerivationAlgorithmIdentifier details/info
asn1obj.Sequence([][]byte{
// kek pbkdf2 Salt
asn1obj.OctetString(kekSalt),
// kek pbkdf2 Iterations
asn1obj.Integer(big.NewInt(apcKEKIterations)),
// kek pbkdf2 hash type
asn1obj.Sequence([][]byte{
asn1obj.ObjectIdentifier(asn1obj.OIDhmacWithSHA256),
asn1.NullBytes,
}),
}),
}),
// keyEncryptionAlgorithm (for CEK)
asn1obj.Sequence([][]byte{
// KeyEncryptionAlgorithmIdentifier
asn1obj.ObjectIdentifier(asn1obj.OIDpwriKEK),
// KeyEncryptionAlgorithmIdentifier details/info
asn1obj.Sequence([][]byte{
// encryption alg
asn1obj.ObjectIdentifier(asn1obj.OIDdesEDE3CBC),
// encryption alg's salt
asn1obj.OctetString(cekEncryptSalt),
}),
}),
// EncryptedKey (the actual ciphertext for the CEK)
asn1obj.OctetString(encryptedCEK),
}),
}),
// EncryptedContentInfo (actual encrypted content)
asn1obj.Sequence([][]byte{
// ContentType
asn1obj.ObjectIdentifier(asn1obj.OIDpkcs7Data),
// encryption alg OBJ
encAlgObj,
// [0] IMPLICIT EncryptedContent (AKA the ciphertext)
asn1obj.ExplicitValue(0, encryptedContent),
}),
// MAC
asn1obj.OctetString(mac),
}
// combine to singular byte slice
finalEnv := []byte{}
for i := range envelope {
finalEnv = append(finalEnv, envelope[i]...)
}
return finalEnv, nil
}

View file

@ -3,6 +3,7 @@ package pkcs15
import (
"apc-p15-tool/pkg/tools/asn1obj"
"crypto/sha1"
"encoding/asn1"
"math/big"
)
@ -13,7 +14,7 @@ func (p15 *pkcs15KeyCert) keyId() []byte {
asn1obj.Sequence([][]byte{
// Key is RSA
asn1obj.ObjectIdentifier(asn1obj.OIDrsaEncryptionPKCS1),
asn1obj.Null(),
asn1.NullBytes,
}),
// BIT STRING of rsa key public key
asn1obj.BitString(
@ -33,3 +34,19 @@ func (p15 *pkcs15KeyCert) keyId() []byte {
return hasher.Sum(nil)
}
// keyIdInt2 returns the sequence for keyId with INT val of 2
// For APC, this appears to be the same value is the base keyId
// but this isn't compliant with the spec which actually seems
// to call for SKID (skid octet value copied directly out of the
// certificate's x509 extension)
func (p15 *pkcs15KeyCert) keyIdInt2() []byte {
// Create Object
obj := asn1obj.Sequence([][]byte{
asn1obj.Integer(big.NewInt(2)),
// Note: This is for APC, doesn't seem compliant with spec though
asn1obj.OctetString(p15.keyId()),
})
return obj
}

View file

@ -1,66 +0,0 @@
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
}

36
pkg/pkcs15/pem_parse.go Normal file
View 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
}

View file

@ -1,36 +1,127 @@
package pkcs15
import (
"crypto/rsa"
"crypto/x509"
"apc-p15-tool/pkg/tools/asn1obj"
"math/big"
)
// 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
}
const (
apcKeyLabel = "Private key"
)
// 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)
// 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
}
// decode / check cert
cert, err := pemCertDecode(certPem, keyPem)
cert, err := p15.toP15Cert()
if err != nil {
return nil, err
}
p15 := &pkcs15KeyCert{
key: key,
cert: cert,
// 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.ExplicitCompound(0, [][]byte{
asn1obj.Sequence([][]byte{
asn1obj.Integer(big.NewInt(0)),
asn1obj.Sequence([][]byte{
asn1obj.ExplicitCompound(0, [][]byte{
asn1obj.ExplicitCompound(0, [][]byte{
pkey,
}),
}),
asn1obj.ExplicitCompound(4, [][]byte{
asn1obj.ExplicitCompound(0, [][]byte{
cert,
}),
}),
}),
}),
}),
})
return p15File, nil
}
// toP15PrivateKey creates the encoded private key. it is broken our from the larger p15
// function for readability
// NOTE: Do not use this to try and turn just a private key into a p15, the format isn't
// quite the same.
func (p15 *pkcs15KeyCert) toP15PrivateKey() ([]byte, error) {
// rsa encrypted key in encrypted envelope
envelope, err := p15.encryptedKeyEnvelope()
if err != nil {
return nil, err
}
return p15, nil
// 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)}),
// 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{
envelope,
}),
}),
}),
})
return key, nil
}
// toP15Cert creates the encoded certificate. it is broken our from the larger p15
// function for readability
// NOTE: Do not use this to try and turn just a cert into a p15. I don't believe,
// such a thing is permissible under the spec.
func (p15 *pkcs15KeyCert) toP15Cert() ([]byte, error) {
// cert object
cert := asn1obj.Sequence([][]byte{
// commonObjectAttributes - Label
asn1obj.Sequence([][]byte{
asn1obj.UTF8String(apcKeyLabel),
}),
// keyIds of various types
asn1obj.Sequence([][]byte{
asn1obj.OctetString(p15.keyId()),
// additional keyids
asn1obj.ExplicitCompound(2, [][]byte{
p15.keyIdInt2(),
// p15.keyIdInt3(),
// p15.keyIdInt6(),
// p15.keyIdInt7(),
// p15.keyIdInt8(),
// p15.keyIdInt9(),
}),
}),
})
return cert, nil
}

24
pkg/pkcs15/private_key.go Normal file
View file

@ -0,0 +1,24 @@
package pkcs15
import "apc-p15-tool/pkg/tools/asn1obj"
// privateKeyObject returns the ASN.1 representation of a private key
func (p15 *pkcs15KeyCert) privateKeyObject() []byte {
// ensure all expected vals are available
p15.key.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),
})
return pkey
}