Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/auth/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ type VersionStorage interface {
// operations.
type RecordingEncryptionManager interface {
services.RecordingEncryption
recordingencryption.DecryptionKeyFinder
recordingencryption.KeyUnwrapper
SetCache(cache recordingencryption.Cache)
}

Expand Down
2 changes: 2 additions & 0 deletions lib/auth/keystore/aws_kms.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ func awsAlgorithm(alg cryptosuites.Algorithm) (kmstypes.KeySpec, error) {
switch alg {
case cryptosuites.RSA2048:
return kmstypes.KeySpecRsa2048, nil
case cryptosuites.RSA4096:
return kmstypes.KeySpecRsa4096, nil
case cryptosuites.ECDSAP256:
return kmstypes.KeySpecEccNistP256, nil
}
Expand Down
2 changes: 2 additions & 0 deletions lib/auth/keystore/aws_kms_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,8 @@ func (f *fakeAWSKMSService) CreateKey(_ context.Context, input *kms.CreateKeyInp
switch input.KeySpec {
case kmstypes.KeySpecRsa2048:
privKeyPEM = testRSA2048PrivateKeyPEM
case kmstypes.KeySpecRsa4096:
privKeyPEM = testRSA4096PrivateKeyPEM
case kmstypes.KeySpecEccNistP256:
signer, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.ECDSAP256)
if err != nil {
Expand Down
25 changes: 21 additions & 4 deletions lib/auth/keystore/gcp_kms.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func (g *gcpKMSKeyStore) keyTypeDescription() string {
}

func (g *gcpKMSKeyStore) generateKey(ctx context.Context, algorithm cryptosuites.Algorithm, usage keyUsage) (gcpKMSKeyID, error) {
alg, err := gcpAlgorithm(algorithm)
alg, err := gcpAlgorithm(usage, algorithm)
if err != nil {
return gcpKMSKeyID{}, trace.Wrap(err)
}
Expand Down Expand Up @@ -176,12 +176,29 @@ func (g *gcpKMSKeyStore) generateDecrypter(ctx context.Context, algorithm crypto
return keyID.marshal(), decrypter, gcpOAEPHash, nil
}

func gcpAlgorithm(alg cryptosuites.Algorithm) (kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm, error) {
func gcpAlgorithm(usage keyUsage, alg cryptosuites.Algorithm) (kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm, error) {
switch alg {
case cryptosuites.RSA2048:
return kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256, nil
switch usage {
case keyUsageSign:
return kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256, nil
case keyUsageDecrypt:
return kmspb.CryptoKeyVersion_RSA_DECRYPT_OAEP_2048_SHA256, nil
}
case cryptosuites.RSA4096:
switch usage {
case keyUsageSign:
return kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256, nil
case keyUsageDecrypt:
return kmspb.CryptoKeyVersion_RSA_DECRYPT_OAEP_4096_SHA256, nil
}
case cryptosuites.ECDSAP256:
return kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256, nil
switch usage {
case keyUsageSign:
return kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256, nil
case keyUsageDecrypt:
return kmspb.CryptoKeyVersion_CRYPTO_KEY_VERSION_ALGORITHM_UNSPECIFIED, trace.BadParameter("unsupported algorithm for decryption: %v", alg)
}
}
return kmspb.CryptoKeyVersion_CRYPTO_KEY_VERSION_ALGORITHM_UNSPECIFIED, trace.BadParameter("unsupported algorithm: %v", alg)
}
Expand Down
2 changes: 2 additions & 0 deletions lib/auth/keystore/gcp_kms_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ func (f *fakeGCPKMSServer) CreateCryptoKey(ctx context.Context, req *kmspb.Creat
switch cryptoKey.VersionTemplate.Algorithm {
case kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256, kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA512:
pem = testRSA2048PrivateKeyPEM
case kmspb.CryptoKeyVersion_RSA_DECRYPT_OAEP_4096_SHA256:
pem = testRSA4096PrivateKeyPEM
case kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256:
signer, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.ECDSAP256)
if err != nil {
Expand Down
14 changes: 11 additions & 3 deletions lib/auth/keystore/pkcs11.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ func (p *pkcs11KeyStore) generateSigner(ctx context.Context, alg cryptosuites.Al
case cryptosuites.RSA2048:
signer, err := p.generateRSA2048(rawPKCS11ID, label)
return rawTeleportID, signer, trace.Wrap(err, "generating RSA2048 key")
case cryptosuites.RSA4096:
signer, err := p.generateRSA4096(rawPKCS11ID, label)
return rawTeleportID, signer, trace.Wrap(err, "generating RSA4096 key")
case cryptosuites.ECDSAP256:
signer, err := p.generateECDSAP256(rawPKCS11ID, label)
return rawTeleportID, signer, trace.Wrap(err, "generating ECDSAP256 key")
Expand Down Expand Up @@ -208,9 +211,9 @@ func (p *pkcs11KeyStore) generateDecrypter(ctx context.Context, alg cryptosuites

label := []byte(p.hostUUID)
switch alg {
case cryptosuites.RSA2048:
decrypter, err := p.generateRSA2048(rawPKCS11ID, label)
return rawTeleportID, newOAEPDecrypter(p.oaepHash, decrypter), p.oaepHash, trace.Wrap(err, "generating RSA2048 key")
case cryptosuites.RSA4096:
decrypter, err := p.generateRSA4096(rawPKCS11ID, label)
return rawTeleportID, newOAEPDecrypter(p.oaepHash, decrypter), p.oaepHash, trace.Wrap(err, "generating RSA4096 key")
default:
return nil, nil, p.oaepHash, trace.BadParameter("unsupported key algorithm for PKCS#11 HSM decryption: %v", alg)
}
Expand All @@ -221,6 +224,11 @@ func (p *pkcs11KeyStore) generateRSA2048(ckaID, label []byte) (crypto11.SignerDe
return signer, trace.Wrap(err)
}

func (p *pkcs11KeyStore) generateRSA4096(ckaID, label []byte) (crypto11.SignerDecrypter, error) {
signer, err := p.ctx.GenerateRSAKeyPairWithLabel(ckaID, label, 4096)
return signer, trace.Wrap(err)
}

func (p *pkcs11KeyStore) generateECDSAP256(ckaID, label []byte) (crypto.Signer, error) {
signer, err := p.ctx.GenerateECDSAKeyPairWithLabel(ckaID, label, elliptic.P256())
return signer, trace.Wrap(err)
Expand Down
7 changes: 1 addition & 6 deletions lib/auth/keystore/software.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,17 +115,12 @@ func (s *softwareKeyStore) generateDecrypter(ctx context.Context, alg cryptosuit
return nil, nil, softwareHash, trace.Wrap(err)
}

decrypter := key
if alg == cryptosuites.RSA2048 {
decrypter = newOAEPDecrypter(softwareHash, decrypter)
}

privateKeyPEM, err := keys.MarshalDecrypter(key)
if err != nil {
return nil, nil, softwareHash, trace.Wrap(err)
}

return privateKeyPEM, decrypter, softwareHash, trace.Wrap(err)
return privateKeyPEM, newOAEPDecrypter(softwareHash, key), softwareHash, trace.Wrap(err)
}

// getSigner returns a crypto.Signer for the given pem-encoded private key.
Expand Down
124 changes: 78 additions & 46 deletions lib/auth/recordingencryption/age.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,44 +18,62 @@ package recordingencryption

import (
"context"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"io"

"filippo.io/age"
"github.com/gravitational/trace"

"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils/keys"
)

// X25519Stanza is the default stanza type used by age.
const X25519Stanza = "X25519"

// RecordingStanza is the type used for the identifying stanza added by RecordingRecipient.
const RecordingStanza = "Recording-X25519"
const RecordingStanza = "teleport-recording-rsa4096"

// oaepLabel must be present during encryption and decryption.
const oaepLabel = "teleport/v1/rsa"

// UnwrapInput represents a request to decrypt a wrapped file key.
type UnwrapInput struct {
// Fingerprint of the public key used to find the related private key.
Fingerprint string
// WrappedKey is the encrypted file key in an encrypted recording stanza.
WrappedKey []byte

// Rand reader to pass to use during decryption.
Rand io.Reader
// Opts that should be used during decryption.
Opts crypto.DecrypterOpts
}

// DecryptionKeyFinder returns an EncryptionKeyPair related to at least one of the given public keys to be used
// for file key unwrapping.
type DecryptionKeyFinder interface {
FindDecryptionKey(ctx context.Context, publicKeys ...[]byte) (*types.EncryptionKeyPair, error)
// KeyUnwrapper returns an unwrapped file key given a wrapped key and a fingerprint of the encryption key.
type KeyUnwrapper interface {
UnwrapKey(ctx context.Context, in UnwrapInput) ([]byte, error)
}

// RecordingIdentity removes public keys from stanzas and passes the unwrap call to the default
// age.X25519Identity.
// RecordingIdentity unwraps file keys using the configured [KeyUnwrapper] and the recording stanzas
// included in the age header.
type RecordingIdentity struct {
ctx context.Context
keyFinder DecryptionKeyFinder
unwrapper KeyUnwrapper
}

// NewRecordingIdentity returns a RecordingIdentity that will use the given DecryptionKeyFinder in order to facilitate
// NewRecordingIdentity returns a new RecordingIdentity using the given [KeyUnwrapper]
// file key unwrapping.
func NewRecordingIdentity(ctx context.Context, keyFinder DecryptionKeyFinder) *RecordingIdentity {
func NewRecordingIdentity(ctx context.Context, unwrapper KeyUnwrapper) *RecordingIdentity {
return &RecordingIdentity{
ctx: ctx,
keyFinder: keyFinder,
unwrapper: unwrapper,
}
}

// Unwrap uses the additional stanzas added by RecordingRecipient.Wrap in order to find a matching X25519 identity.
// Unwrap uses the additional stanzas added by [RecordingRecipient.Wrap] in order to find a matching RSA 4096
// private key.
func (i *RecordingIdentity) Unwrap(stanzas []*age.Stanza) ([]byte, error) {
var publicKeys [][]byte
var errs []error
for _, stanza := range stanzas {
if stanza.Type != RecordingStanza {
continue
Expand All @@ -65,55 +83,69 @@ func (i *RecordingIdentity) Unwrap(stanzas []*age.Stanza) ([]byte, error) {
continue
}

publicKeys = append(publicKeys, []byte(stanza.Args[0]))
}
fileKey, err := i.unwrapper.UnwrapKey(i.ctx, UnwrapInput{
Rand: rand.Reader,
WrappedKey: stanza.Body,
Fingerprint: stanza.Args[0],
Opts: &rsa.OAEPOptions{
Hash: crypto.SHA256,
Label: []byte(oaepLabel),
},
})
if err != nil {
if !trace.IsNotFound(err) {
errs = append(errs, err)
}
continue
}

pair, err := i.keyFinder.FindDecryptionKey(i.ctx, publicKeys...)
if err != nil {
return nil, trace.Wrap(err)
return fileKey, nil
}

identity, err := age.ParseX25519Identity(string(pair.PrivateKey))
if err != nil {
return nil, trace.Wrap(err)
if len(errs) == 0 {
return nil, trace.Errorf("could not find an accessible decrypter for unwrapping")
}

return identity.Unwrap(stanzas)
return nil, trace.NewAggregate(errs...)
}

// RecordingRecipient adds the public key to the stanzas generated by the default age.X25519Recipient
// RecordingRecipient wraps file keys using an RSA 40960public key.
type RecordingRecipient struct {
*age.X25519Recipient
*rsa.PublicKey
}

// ParseRecordingRecipient parses an Bech32 encoded age X25519 public key into a RecordingRecipient.
func ParseRecordingRecipient(s string) (*RecordingRecipient, error) {
recipient, err := age.ParseX25519Recipient(s)
// ParseRecordingRecipient parses a PEM encoded RSA 4096 public key into a RecordingRecipient.
func ParseRecordingRecipient(in []byte) (*RecordingRecipient, error) {
pubKey, err := keys.ParsePublicKey(in)
if err != nil {
return nil, trace.Wrap(err)
}

return &RecordingRecipient{X25519Recipient: recipient}, nil
rsaKey, ok := pubKey.(*rsa.PublicKey)
if !ok {
return nil, trace.BadParameter("recording encryption key must be a public RSA 4096")
}

return &RecordingRecipient{PublicKey: rsaKey}, nil
}

// Wrap a fileKey using the wrapped X2519Recipient. An additional stanza containing the bech32 encoded X25519
// public key will be created to enable lookups during Unwrap.
// Wrap a fileKey using an RSA public key. The fingerprint of the key will be included in the stanza
// to aid in fetching the correct private key during [Unwrap].
func (r *RecordingRecipient) Wrap(fileKey []byte) ([]*age.Stanza, error) {
stanzas, err := r.X25519Recipient.Wrap(fileKey)
cipher, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, r.PublicKey, fileKey, []byte(oaepLabel))
if err != nil {
return nil, trace.Wrap(err)
}

// a new stanza has to be added because modifying the original stanza and returning it to "normal" during
// Unwrap fails due to MAC errors
for _, stanza := range stanzas {
if stanza.Type == X25519Stanza {
stanzas = append(stanzas, &age.Stanza{
Type: RecordingStanza,
Args: []string{r.String()},
})
}
fp, err := Fingerprint(r.PublicKey)
if err != nil {
return nil, trace.Wrap(err)
}

return stanzas, nil
return []*age.Stanza{
{
Type: RecordingStanza,
Args: []string{fp},
Body: cipher,
},
}, nil
}
Loading
Loading