mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-11 16:17:39 +02:00
cryptutil: always use kek public id, add x509 support (#2066)
This commit is contained in:
parent
294addd857
commit
9de340b48b
5 changed files with 200 additions and 13 deletions
|
@ -22,5 +22,5 @@ func (o *Options) GetAuditKey() (*cryptutil.PublicKeyEncryptionKey, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return cryptutil.NewPublicKeyEncryptionKey(o.AuditKey.ID, raw)
|
return cryptutil.NewPublicKeyEncryptionKeyWithID(o.AuditKey.ID, raw)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,20 +36,18 @@ const KeyEncryptionKeySize = curve25519.ScalarSize
|
||||||
|
|
||||||
// PrivateKeyEncryptionKey is a Curve25519 asymmetric private encryption key used to decrypt data encryption keys.
|
// PrivateKeyEncryptionKey is a Curve25519 asymmetric private encryption key used to decrypt data encryption keys.
|
||||||
type PrivateKeyEncryptionKey struct {
|
type PrivateKeyEncryptionKey struct {
|
||||||
id string
|
|
||||||
data [KeyEncryptionKeySize]byte
|
data [KeyEncryptionKeySize]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*PrivateKeyEncryptionKey) isKeyEncryptionKey() {}
|
func (*PrivateKeyEncryptionKey) isKeyEncryptionKey() {}
|
||||||
|
|
||||||
// NewPrivateKeyEncryptionKey creates a new encryption key from existing bytes.
|
// NewPrivateKeyEncryptionKey creates a new encryption key from existing bytes.
|
||||||
func NewPrivateKeyEncryptionKey(id string, raw []byte) (*PrivateKeyEncryptionKey, error) {
|
func NewPrivateKeyEncryptionKey(raw []byte) (*PrivateKeyEncryptionKey, error) {
|
||||||
if len(raw) != KeyEncryptionKeySize {
|
if len(raw) != KeyEncryptionKeySize {
|
||||||
return nil, fmt.Errorf("cryptutil: invalid key encryption key, expected %d bytes, got %d",
|
return nil, fmt.Errorf("cryptutil: invalid key encryption key, expected %d bytes, got %d",
|
||||||
KeyEncryptionKeySize, len(raw))
|
KeyEncryptionKeySize, len(raw))
|
||||||
}
|
}
|
||||||
kek := new(PrivateKeyEncryptionKey)
|
kek := new(PrivateKeyEncryptionKey)
|
||||||
kek.id = id
|
|
||||||
copy(kek.data[:], raw)
|
copy(kek.data[:], raw)
|
||||||
return kek, nil
|
return kek, nil
|
||||||
}
|
}
|
||||||
|
@ -57,8 +55,7 @@ func NewPrivateKeyEncryptionKey(id string, raw []byte) (*PrivateKeyEncryptionKey
|
||||||
// GenerateKeyEncryptionKey generates a new random key encryption key.
|
// GenerateKeyEncryptionKey generates a new random key encryption key.
|
||||||
func GenerateKeyEncryptionKey() (*PrivateKeyEncryptionKey, error) {
|
func GenerateKeyEncryptionKey() (*PrivateKeyEncryptionKey, error) {
|
||||||
raw := randomBytes(KeyEncryptionKeySize)
|
raw := randomBytes(KeyEncryptionKeySize)
|
||||||
id := GetKeyEncryptionKeyID(raw)
|
return NewPrivateKeyEncryptionKey(raw)
|
||||||
return NewPrivateKeyEncryptionKey(id, raw)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKeyEncryptionKeyID derives an id from the key encryption key data itself.
|
// GetKeyEncryptionKeyID derives an id from the key encryption key data itself.
|
||||||
|
@ -89,7 +86,7 @@ func (kek *PrivateKeyEncryptionKey) DecryptDataEncryptionKey(ciphertext []byte)
|
||||||
|
|
||||||
// ID returns the private key's id.
|
// ID returns the private key's id.
|
||||||
func (kek *PrivateKeyEncryptionKey) ID() string {
|
func (kek *PrivateKeyEncryptionKey) ID() string {
|
||||||
return kek.id
|
return kek.Public().id
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyBytes returns the private key encryption key's raw bytes.
|
// KeyBytes returns the private key encryption key's raw bytes.
|
||||||
|
@ -104,7 +101,10 @@ func (kek *PrivateKeyEncryptionKey) Public() *PublicKeyEncryptionKey {
|
||||||
// taken from NACL box.GenerateKey
|
// taken from NACL box.GenerateKey
|
||||||
var publicKey [32]byte
|
var publicKey [32]byte
|
||||||
curve25519.ScalarBaseMult(&publicKey, &kek.data)
|
curve25519.ScalarBaseMult(&publicKey, &kek.data)
|
||||||
return &PublicKeyEncryptionKey{id: kek.id, data: publicKey}
|
return &PublicKeyEncryptionKey{
|
||||||
|
id: GetKeyEncryptionKeyID(kek.data[:]),
|
||||||
|
data: publicKey,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PublicKeyEncryptionKey is a Curve25519 asymmetric public encryption key used to encrypt data encryption keys.
|
// PublicKeyEncryptionKey is a Curve25519 asymmetric public encryption key used to encrypt data encryption keys.
|
||||||
|
@ -116,7 +116,12 @@ type PublicKeyEncryptionKey struct {
|
||||||
func (*PublicKeyEncryptionKey) isKeyEncryptionKey() {}
|
func (*PublicKeyEncryptionKey) isKeyEncryptionKey() {}
|
||||||
|
|
||||||
// NewPublicKeyEncryptionKey creates a new encryption key from existing bytes.
|
// NewPublicKeyEncryptionKey creates a new encryption key from existing bytes.
|
||||||
func NewPublicKeyEncryptionKey(id string, raw []byte) (*PublicKeyEncryptionKey, error) {
|
func NewPublicKeyEncryptionKey(raw []byte) (*PublicKeyEncryptionKey, error) {
|
||||||
|
return NewPublicKeyEncryptionKeyWithID(GetKeyEncryptionKeyID(raw), raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPublicKeyEncryptionKeyWithID creates a new encryption key from an existing id and bytes.
|
||||||
|
func NewPublicKeyEncryptionKeyWithID(id string, raw []byte) (*PublicKeyEncryptionKey, error) {
|
||||||
if len(raw) != KeyEncryptionKeySize {
|
if len(raw) != KeyEncryptionKeySize {
|
||||||
return nil, fmt.Errorf("cryptutil: invalid key encryption key, expected %d bytes, got %d",
|
return nil, fmt.Errorf("cryptutil: invalid key encryption key, expected %d bytes, got %d",
|
||||||
KeyEncryptionKeySize, len(raw))
|
KeyEncryptionKeySize, len(raw))
|
||||||
|
|
|
@ -21,7 +21,7 @@ func TestKeyEncryptionKey(t *testing.T) {
|
||||||
t.Run("anonymous", func(t *testing.T) {
|
t.Run("anonymous", func(t *testing.T) {
|
||||||
kek, err := GenerateKeyEncryptionKey()
|
kek, err := GenerateKeyEncryptionKey()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
kekPublic, err := NewPublicKeyEncryptionKey(kek.ID(), kek.Public().KeyBytes())
|
kekPublic, err := NewPublicKeyEncryptionKey(kek.Public().KeyBytes())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
ciphertext, err := kekPublic.Encrypt([]byte("HELLO WORLD"))
|
ciphertext, err := kekPublic.Encrypt([]byte("HELLO WORLD"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -43,7 +43,7 @@ func TestKeyEncryptionKey(t *testing.T) {
|
||||||
t.Run("ID", func(t *testing.T) {
|
t.Run("ID", func(t *testing.T) {
|
||||||
kek, err := GenerateKeyEncryptionKey()
|
kek, err := GenerateKeyEncryptionKey()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, kek.id, kek.ID())
|
assert.Equal(t, kek.Public().id, kek.ID())
|
||||||
})
|
})
|
||||||
t.Run("KeyBytes", func(t *testing.T) {
|
t.Run("KeyBytes", func(t *testing.T) {
|
||||||
private, err := GenerateKeyEncryptionKey()
|
private, err := GenerateKeyEncryptionKey()
|
||||||
|
@ -60,12 +60,12 @@ func TestKeyEncryptionKey(t *testing.T) {
|
||||||
})
|
})
|
||||||
t.Run("invalid key", func(t *testing.T) {
|
t.Run("invalid key", func(t *testing.T) {
|
||||||
t.Run("private", func(t *testing.T) {
|
t.Run("private", func(t *testing.T) {
|
||||||
kek, err := NewPrivateKeyEncryptionKey("TEST", []byte("NOT BIG ENOUGH"))
|
kek, err := NewPrivateKeyEncryptionKey([]byte("NOT BIG ENOUGH"))
|
||||||
require.Nil(t, kek)
|
require.Nil(t, kek)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
t.Run("public", func(t *testing.T) {
|
t.Run("public", func(t *testing.T) {
|
||||||
kek, err := NewPublicKeyEncryptionKey("TEST", []byte("NOT BIG ENOUGH"))
|
kek, err := NewPublicKeyEncryptionKey([]byte("NOT BIG ENOUGH"))
|
||||||
require.Nil(t, kek)
|
require.Nil(t, kek)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
123
pkg/cryptutil/x509.go
Normal file
123
pkg/cryptutil/x509.go
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
package cryptutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://tools.ietf.org/id/draft-ietf-curdle-pkix-05.html#rfc.section.3
|
||||||
|
var oidPublicKeyX25519 = asn1.ObjectIdentifier{1, 3, 101, 110}
|
||||||
|
|
||||||
|
// from x509, used for ASN.1
|
||||||
|
type (
|
||||||
|
pkcs8 struct {
|
||||||
|
Version int
|
||||||
|
Algo pkix.AlgorithmIdentifier
|
||||||
|
PrivateKey []byte
|
||||||
|
}
|
||||||
|
pkixPublicKey struct {
|
||||||
|
Algo pkix.AlgorithmIdentifier
|
||||||
|
BitString asn1.BitString
|
||||||
|
}
|
||||||
|
publicKeyInfo struct {
|
||||||
|
Raw asn1.RawContent
|
||||||
|
Algorithm pkix.AlgorithmIdentifier
|
||||||
|
PublicKey asn1.BitString
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// MarshalPKCS8PrivateKey wraps x509.MarshalPKCS8PrivateKey with added support for KeyEncryptionKeys.
|
||||||
|
func MarshalPKCS8PrivateKey(key interface{}) ([]byte, error) {
|
||||||
|
// also support a pointer to a private key encryption key
|
||||||
|
if kek, ok := key.(*PrivateKeyEncryptionKey); ok {
|
||||||
|
key = *kek
|
||||||
|
}
|
||||||
|
if kek, ok := key.(PrivateKeyEncryptionKey); ok {
|
||||||
|
var privKey pkcs8
|
||||||
|
privKey.Algo = pkix.AlgorithmIdentifier{
|
||||||
|
Algorithm: oidPublicKeyX25519,
|
||||||
|
}
|
||||||
|
curvePrivateKey, err := asn1.Marshal(kek.KeyBytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cryptutil: failed to marshal private key: %v", err)
|
||||||
|
}
|
||||||
|
privKey.PrivateKey = curvePrivateKey
|
||||||
|
return asn1.Marshal(privKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fall back to the original MarshalPKCS8PrivateKey
|
||||||
|
return x509.MarshalPKCS8PrivateKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalPKIXPublicKey wraps x509.MarshalPKIXPublicKey with added support for KeyEncryptionKeys.
|
||||||
|
func MarshalPKIXPublicKey(pub interface{}) ([]byte, error) {
|
||||||
|
if kek, ok := pub.(*PublicKeyEncryptionKey); ok {
|
||||||
|
pub = *kek
|
||||||
|
}
|
||||||
|
if kek, ok := pub.(PublicKeyEncryptionKey); ok {
|
||||||
|
val := pkixPublicKey{
|
||||||
|
Algo: pkix.AlgorithmIdentifier{
|
||||||
|
Algorithm: oidPublicKeyX25519,
|
||||||
|
},
|
||||||
|
BitString: asn1.BitString{
|
||||||
|
Bytes: kek.KeyBytes(),
|
||||||
|
BitLength: 8 * len(kek.KeyBytes()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ret, _ := asn1.Marshal(val)
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fall back to the original MarshalPKIXPublicKey
|
||||||
|
return x509.MarshalPKIXPublicKey(pub)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePKCS8PrivateKey wraps x509.ParsePKCS8PrivateKey with added support for KeyEncryptionKeys.
|
||||||
|
func ParsePKCS8PrivateKey(der []byte) (interface{}, error) {
|
||||||
|
var privKey pkcs8
|
||||||
|
_, err := asn1.Unmarshal(der, &privKey)
|
||||||
|
if err != nil {
|
||||||
|
return x509.ParsePKCS8PrivateKey(der)
|
||||||
|
}
|
||||||
|
|
||||||
|
if privKey.Algo.Algorithm.Equal(oidPublicKeyX25519) {
|
||||||
|
var bs []byte
|
||||||
|
if _, err := asn1.Unmarshal(privKey.PrivateKey, &bs); err != nil {
|
||||||
|
return nil, fmt.Errorf("cryptutil: invalid X25519 private key: %v", err)
|
||||||
|
}
|
||||||
|
return NewPrivateKeyEncryptionKey(bs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to the original ParsePKCS8PrivateKey
|
||||||
|
return x509.ParsePKCS8PrivateKey(der)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePKIXPublicKey wraps x509.ParsePKIXPublicKey with added support for KeyEncryptionKeys.
|
||||||
|
func ParsePKIXPublicKey(derBytes []byte) (pub interface{}, err error) {
|
||||||
|
var pki publicKeyInfo
|
||||||
|
rest, err := asn1.Unmarshal(derBytes, &pki)
|
||||||
|
if err != nil || len(rest) > 0 {
|
||||||
|
return x509.ParsePKIXPublicKey(derBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pki.Algorithm.Algorithm.Equal(oidPublicKeyX25519) {
|
||||||
|
asn1Data := pki.PublicKey.RightAlign()
|
||||||
|
// RFC 8410, Section 3
|
||||||
|
// > For all of the OIDs, the parameters MUST be absent.
|
||||||
|
if len(pki.Algorithm.Parameters.FullBytes) != 0 {
|
||||||
|
return nil, errors.New("cryptutil: x25519 key encoded with illegal parameters")
|
||||||
|
}
|
||||||
|
if len(asn1Data) != KeyEncryptionKeySize {
|
||||||
|
return nil, errors.New("cryptutil: wrong x25519 public key size")
|
||||||
|
}
|
||||||
|
pub := make([]byte, KeyEncryptionKeySize)
|
||||||
|
copy(pub, asn1Data)
|
||||||
|
return NewPublicKeyEncryptionKey(pub)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fall back to the original ParsePKIXPublicKey
|
||||||
|
return x509.ParsePKIXPublicKey(derBytes)
|
||||||
|
}
|
59
pkg/cryptutil/x509_test.go
Normal file
59
pkg/cryptutil/x509_test.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package cryptutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/pem"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// generated using:
|
||||||
|
// openssl genpkey -algorithm x25519 -out priv.pem
|
||||||
|
// openssl pkey -in priv.pem -out pub.pem -pubout
|
||||||
|
var (
|
||||||
|
rawPrivateX25519Key = []byte(`-----BEGIN PRIVATE KEY-----
|
||||||
|
MC4CAQAwBQYDK2VuBCIEIKALoNgzCksH0v0Bc7Ghl8vGin4MAIKpmtZSmaMN0Vtb
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
`)
|
||||||
|
rawPublicX25519Key = []byte(`-----BEGIN PUBLIC KEY-----
|
||||||
|
MCowBQYDK2VuAyEAk63g8PY1JJTkrranWTxGSd/yA5kAgJlPk4/srMKg9mg=
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
`)
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPKCS8PrivateKey(t *testing.T) {
|
||||||
|
block, _ := pem.Decode(rawPrivateX25519Key)
|
||||||
|
|
||||||
|
kek, err := ParsePKCS8PrivateKey(block.Bytes)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, &PrivateKeyEncryptionKey{}, kek)
|
||||||
|
|
||||||
|
t.Run("marshal", func(t *testing.T) {
|
||||||
|
der, err := MarshalPKCS8PrivateKey(kek)
|
||||||
|
require.NoError(t, err)
|
||||||
|
actual := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "PRIVATE KEY",
|
||||||
|
Bytes: der,
|
||||||
|
})
|
||||||
|
assert.Equal(t, rawPrivateX25519Key, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPKIXPublicKey(t *testing.T) {
|
||||||
|
block, _ := pem.Decode(rawPublicX25519Key)
|
||||||
|
|
||||||
|
kek, err := ParsePKIXPublicKey(block.Bytes)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, &PublicKeyEncryptionKey{}, kek)
|
||||||
|
|
||||||
|
t.Run("marshal", func(t *testing.T) {
|
||||||
|
der, err := MarshalPKIXPublicKey(kek)
|
||||||
|
require.NoError(t, err)
|
||||||
|
actual := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "PUBLIC KEY",
|
||||||
|
Bytes: der,
|
||||||
|
})
|
||||||
|
assert.Equal(t, rawPublicX25519Key, actual)
|
||||||
|
})
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue