From 6439d62cbef03a5b58f09b9b26f046406592eaa8 Mon Sep 17 00:00:00 2001 From: Nic Klaassen Date: Mon, 26 Aug 2024 18:06:15 -0700 Subject: [PATCH] sign SSH certs with ssh-sha2-256 by default --- lib/auth/keystore/aws_kms.go | 11 +- lib/auth/keystore/aws_kms_test.go | 2 +- lib/auth/keystore/gcp_kms.go | 41 ++++--- lib/auth/keystore/gcp_kms_test.go | 4 +- lib/auth/keystore/keystore_test.go | 29 ++++- lib/auth/keystore/manager.go | 117 +++++++++++------- lib/auth/keystore/pkcs11.go | 2 +- lib/auth/keystore/software.go | 2 +- lib/auth/keystore/ssh_signature_test.go | 151 ++++++++++++++++++++++++ 9 files changed, 284 insertions(+), 75 deletions(-) create mode 100644 lib/auth/keystore/ssh_signature_test.go diff --git a/lib/auth/keystore/aws_kms.go b/lib/auth/keystore/aws_kms.go index bf8b1556cb8e7..fd599336ec0af 100644 --- a/lib/auth/keystore/aws_kms.go +++ b/lib/auth/keystore/aws_kms.go @@ -117,7 +117,7 @@ func (a *awsKMSKeystore) keyTypeDescription() string { // generateKey creates a new private key and returns its identifier and a crypto.Signer. The returned // identifier can be passed to getSigner later to get an equivalent crypto.Signer. -func (a *awsKMSKeystore) generateKey(ctx context.Context, algorithm cryptosuites.Algorithm, opts ...rsaKeyOption) ([]byte, crypto.Signer, error) { +func (a *awsKMSKeystore) generateKey(ctx context.Context, algorithm cryptosuites.Algorithm) ([]byte, crypto.Signer, error) { alg, err := awsAlgorithm(algorithm) if err != nil { return nil, nil, trace.Wrap(err) @@ -269,7 +269,14 @@ func (a *awsKMSSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpt return nil, trace.BadParameter("unsupported hash func %q for AWS KMS key type %T", opts.HashFunc(), a.pub) } case crypto.SHA512: - signingAlg = kms.SigningAlgorithmSpecRsassaPkcs1V15Sha512 + switch a.pub.(type) { + case *rsa.PublicKey: + signingAlg = kms.SigningAlgorithmSpecRsassaPkcs1V15Sha512 + case *ecdsa.PublicKey: + signingAlg = kms.SigningAlgorithmSpecEcdsaSha512 + default: + return nil, trace.BadParameter("unsupported hash func %q for AWS KMS key type %T", opts.HashFunc(), a.pub) + } default: return nil, trace.BadParameter("unsupported hash func %q for AWS KMS key", opts.HashFunc()) } diff --git a/lib/auth/keystore/aws_kms_test.go b/lib/auth/keystore/aws_kms_test.go index 5adaa6927facb..5b77367b7c2c2 100644 --- a/lib/auth/keystore/aws_kms_test.go +++ b/lib/auth/keystore/aws_kms_test.go @@ -264,7 +264,7 @@ func (f *fakeAWSKMSService) CreateKey(input *kms.CreateKeyInput) (*kms.CreateKey var privKeyPEM []byte switch aws.StringValue(input.KeySpec) { case kms.KeySpecRsa2048: - privKeyPEM = testRSAPrivateKeyPEM + privKeyPEM = testRSA2048PrivateKeyPEM case kms.KeySpecEccNistP256: signer, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.ECDSAP256) if err != nil { diff --git a/lib/auth/keystore/gcp_kms.go b/lib/auth/keystore/gcp_kms.go index 47e40719ca71c..917487f5fa1da 100644 --- a/lib/auth/keystore/gcp_kms.go +++ b/lib/auth/keystore/gcp_kms.go @@ -113,8 +113,8 @@ func (g *gcpKMSKeyStore) keyTypeDescription() string { // generateKey creates a new private key and returns its identifier and a crypto.Signer. The returned // identifier for gcpKMSKeyStore encodes the full GCP KMS key version name, and can be passed to getSigner // later to get an equivalent crypto.Signer. -func (g *gcpKMSKeyStore) generateKey(ctx context.Context, algorithm cryptosuites.Algorithm, opts ...rsaKeyOption) ([]byte, crypto.Signer, error) { - alg, err := gcpAlgorithm(algorithm, opts...) +func (g *gcpKMSKeyStore) generateKey(ctx context.Context, algorithm cryptosuites.Algorithm) ([]byte, crypto.Signer, error) { + alg, err := gcpAlgorithm(algorithm) if err != nil { return nil, nil, trace.Wrap(err) } @@ -152,22 +152,10 @@ func (g *gcpKMSKeyStore) generateKey(ctx context.Context, algorithm cryptosuites return keyID.marshal(), signer, nil } -func gcpAlgorithm(alg cryptosuites.Algorithm, opts ...rsaKeyOption) (kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm, error) { - rsaOpts := &rsaKeyOptions{} - for _, opt := range opts { - opt(rsaOpts) - } - +func gcpAlgorithm(alg cryptosuites.Algorithm) (kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm, error) { switch alg { case cryptosuites.RSA2048: - switch rsaOpts.digestAlgorithm { - case crypto.SHA256, 0: - return kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256, nil - case crypto.SHA512: - return kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA512, nil - default: - return kmspb.CryptoKeyVersion_CRYPTO_KEY_VERSION_ALGORITHM_UNSPECIFIED, trace.BadParameter("unsupported digest algorithm: %v", rsaOpts.digestAlgorithm) - } + return kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256, nil case cryptosuites.ECDSAP256: return kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256, nil } @@ -360,16 +348,26 @@ func (s *kmsSigner) Public() crypto.PublicKey { } func (s *kmsSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { - requestDigest := &kmspb.Digest{} + var ( + requestDigest *kmspb.Digest + data []byte + ) switch opts.HashFunc() { case crypto.SHA256: - requestDigest.Digest = &kmspb.Digest_Sha256{ - Sha256: digest, + requestDigest = &kmspb.Digest{ + Digest: &kmspb.Digest_Sha256{ + Sha256: digest, + }, } case crypto.SHA512: - requestDigest.Digest = &kmspb.Digest_Sha512{ - Sha512: digest, + requestDigest = &kmspb.Digest{ + Digest: &kmspb.Digest_Sha512{ + Sha512: digest, + }, } + case crypto.Hash(0): + // Ed25519 uses no hash and sends the full raw data. + data = digest default: return nil, trace.BadParameter("unsupported hash func for GCP KMS signer: %v", opts.HashFunc()) } @@ -377,6 +375,7 @@ func (s *kmsSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) resp, err := doGCPRequest(s.ctx, s.g, s.g.kmsClient.AsymmetricSign, &kmspb.AsymmetricSignRequest{ Name: s.keyID.keyVersionName, Digest: requestDigest, + Data: data, }) if err != nil { return nil, trace.Wrap(err, "error while attempting GCP KMS signing operation") diff --git a/lib/auth/keystore/gcp_kms_test.go b/lib/auth/keystore/gcp_kms_test.go index 240b0a453c5ba..fc6a44c5a45d5 100644 --- a/lib/auth/keystore/gcp_kms_test.go +++ b/lib/auth/keystore/gcp_kms_test.go @@ -111,7 +111,7 @@ func (f *fakeGCPKMSServer) CreateCryptoKey(ctx context.Context, req *kmspb.Creat var pem []byte switch cryptoKey.VersionTemplate.Algorithm { case kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256, kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA512: - pem = testRSAPrivateKeyPEM + pem = testRSA2048PrivateKeyPEM case kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256: signer, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.ECDSAP256) if err != nil { @@ -499,7 +499,7 @@ func TestGCPKMSKeystore(t *testing.T) { require.NoError(t, err, "unexpected error creating CA") // Client private key that will be the basis of test certs to be signed. - clientPrivKey, err := keys.ParsePrivateKey(testRSAPrivateKeyPEM) + clientPrivKey, err := keys.ParsePrivateKey(testRSA2048PrivateKeyPEM) require.NoError(t, err) // Test signing an SSH certificate. diff --git a/lib/auth/keystore/keystore_test.go b/lib/auth/keystore/keystore_test.go index d6a9001cc6f97..7537c7cc5d4f1 100644 --- a/lib/auth/keystore/keystore_test.go +++ b/lib/auth/keystore/keystore_test.go @@ -53,7 +53,7 @@ const ( ) var ( - testRSAPrivateKeyPEM = []byte(`-----BEGIN RSA PRIVATE KEY----- + testRSA2048PrivateKeyPEM = []byte(`-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAqiD2rRJ5kq7hP55eOCM9DtdkWPMI8PBKgxaAiQ9J9YF3aNur 98b8kACcTQ8ixSkHsLccVqRdt/Cnb7jtBSrwxJ9BN09fZEiyCvy7lwxNGBMQEaov 9UU722nvuWKb+EkHzcVV9ie9i8wM88xpzzYO8eda8FZjHxaaoe2lkrHiiOFQRubJ @@ -116,17 +116,17 @@ JhuTMEqUaAOZBoQLn+txjl3nu9WwTThJzlY0L4w= testRawSSHKeyPair = &types.SSHKeyPair{ PublicKey: testRSASSHPublicKey, - PrivateKey: testRSAPrivateKeyPEM, + PrivateKey: testRSA2048PrivateKeyPEM, PrivateKeyType: types.PrivateKeyType_RAW, } testRawTLSKeyPair = &types.TLSKeyPair{ Cert: testRSACert, - Key: testRSAPrivateKeyPEM, + Key: testRSA2048PrivateKeyPEM, KeyType: types.PrivateKeyType_RAW, } testRawJWTKeyPair = &types.JWTKeyPair{ PublicKey: testRSAPublicKeyPEM, - PrivateKey: testRSAPrivateKeyPEM, + PrivateKey: testRSA2048PrivateKeyPEM, PrivateKeyType: types.PrivateKeyType_RAW, } @@ -271,6 +271,11 @@ func TestManager(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) + sshSubjectKey, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + sshSubjectPubKey, err := ssh.NewPublicKey(sshSubjectKey) + require.NoError(t, err) + pack := newTestPack(ctx, t) for _, backendDesc := range pack.backends { @@ -335,6 +340,20 @@ func TestManager(t *testing.T) { require.NoError(t, err) require.Equal(t, jwtKeyPair.PublicKey, pubkeyPem) + // Try signing an SSH cert. + sshCert := ssh.Certificate{ + Key: sshSubjectPubKey, + ValidBefore: uint64(time.Now().Add(time.Hour).Unix()), + } + require.NoError(t, sshCert.SignCert(rand.Reader, sshSigner)) + // Verify the signature. + checker := ssh.CertChecker{ + IsUserAuthority: func(pub ssh.PublicKey) bool { + return pub == sshSigner.PublicKey() + }, + } + require.NoError(t, checker.CheckCert("root", &sshCert)) + // Test what happens when the CA has only raw keys, which will be the // initial state when migrating from software to a HSM/KMS backend. ca, err = types.NewCertAuthority(types.CertAuthoritySpecV2{ @@ -544,7 +563,7 @@ func newTestPack(ctx context.Context, t *testing.T) *testPack { config: servicecfg.KeystoreConfig{}, opts: &baseOpts, backend: softwareBackend, - unusedRawKey: testRSAPrivateKeyPEM, + unusedRawKey: testRSA2048PrivateKeyPEM, deletionDoesNothing: true, }) diff --git a/lib/auth/keystore/manager.go b/lib/auth/keystore/manager.go index d26749089464e..0d4ad1a0aef95 100644 --- a/lib/auth/keystore/manager.go +++ b/lib/auth/keystore/manager.go @@ -22,6 +22,10 @@ import ( "bytes" "context" "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/pem" @@ -105,26 +109,12 @@ type Manager struct { authPrefGetter cryptosuites.AuthPreferenceGetter } -// rsaKeyOptions configure options for RSA key generation. -type rsaKeyOptions struct { - digestAlgorithm crypto.Hash -} - -// rsaKeyOption is a functional option for RSA key generation. -type rsaKeyOption func(*rsaKeyOptions) - -func withRSADigestAlgorithm(alg crypto.Hash) rsaKeyOption { - return func(opts *rsaKeyOptions) { - opts.digestAlgorithm = alg - } -} - // backend is an interface that holds private keys and provides signing // operations. type backend interface { // generateRSA creates a new key pair and returns its identifier and a crypto.Signer. The returned // identifier can be passed to getSigner later to get an equivalent crypto.Signer. - generateKey(context.Context, cryptosuites.Algorithm, ...rsaKeyOption) (keyID []byte, signer crypto.Signer, err error) + generateKey(context.Context, cryptosuites.Algorithm) (keyID []byte, signer crypto.Signer, err error) // getSigner returns a crypto.Signer for the given key identifier, if it is found. // The public key is passed as well so that it does not need to be fetched @@ -290,14 +280,7 @@ func (m *Manager) getSSHSigner(ctx context.Context, keySet types.CAKeySet) (ssh. return nil, trace.Wrap(err) } signer = &cryptoCountSigner{Signer: signer, keyType: keyTypeSSH, store: backend.name()} - sshSigner, err := ssh.NewSignerFromSigner(signer) - if err != nil { - return nil, trace.Wrap(err) - } - if sshSigner.PublicKey().Type() == ssh.KeyAlgoRSA { - // SHA-512 to match NewSSHKeyPair. - sshSigner = toRSASHA512Signer(sshSigner) - } + sshSigner, err := sshSignerFromCryptoSigner(signer) return sshSigner, trace.Wrap(err) } } @@ -316,22 +299,74 @@ func publicKeyFromSSHAuthorizedKey(sshAuthorizedKey []byte) (crypto.PublicKey, e return cryptoPublicKey.CryptoPublicKey(), nil } -// toRSASHA512Signer forces an ssh.MultiAlgorithmSigner into using -// "rsa-sha2-sha512" (instead of its SHA256 default). -func toRSASHA512Signer(signer ssh.Signer) ssh.Signer { - ss, ok := signer.(ssh.MultiAlgorithmSigner) +func sshSignerFromCryptoSigner(cryptoSigner crypto.Signer) (ssh.Signer, error) { + sshSigner, err := ssh.NewSignerFromSigner(cryptoSigner) + if err != nil { + return nil, trace.Wrap(err) + } + // [ssh.NewSignerFromSigner] currently always returns an [ssh.AlgorithmSigner]. + algorithmSigner, ok := sshSigner.(ssh.AlgorithmSigner) if !ok { - return signer + return nil, trace.BadParameter("SSH CA: unsupported key type: %s", sshSigner.PublicKey().Type()) + } + // Note: we don't actually create keys with all the algorithms supported + // below, but customers have been known to import their own existing keys. + switch pub := cryptoSigner.Public().(type) { + case *rsa.PublicKey: + // The current default hash used in ssh.(*Certificate).SignCert for an + // RSA signer created via ssh.NewSignerFromSigner is always SHA256, + // irrespective of the key size. + // This was a change in golang.org/x/crypto 0.14.0, prior to that the + // default was always SHA512. + // + // Due to the historical SHA512 default that existed at a time when + // hash algorithm selection was much more difficult, there are many + // existing GCP KMS keys that were created as 4096-bit keys using a + // SHA512 hash. GCP KMS is very particular about RSA hash algorithms: + // - 2048-bit or 3072-bit keys *must* use SHA256 + // - 4096-bit keys *must* use SHA256 or SHA512 + // - the hash length must be set *when the key is created* and can't be + // changed. + // + // The chosen signature algorithms below are necessary to support + // existing GCP KMS keys, but they are also reasonable defaults for keys + // outside of GCP KMS. + // + // [rsa.PublicKey.Size()] returns 256 for a 2048-bit key; more generally + // it always returns the bit length divided by 8. + keySize := pub.Size() + switch { + case keySize < 256: + return nil, trace.BadParameter("SSH CA: RSA key size (%d) is too small", keySize) + case keySize < 512: + // This case matches 2048 and 3072 bit GCP KMS keys which *must* use SHA256. + return ssh.NewSignerWithAlgorithms(algorithmSigner, []string{ssh.KeyAlgoRSASHA256}) + default: + // This case matches existing 4096 bit GCP KMS keys which *must* use SHA512 + return ssh.NewSignerWithAlgorithms(algorithmSigner, []string{ssh.KeyAlgoRSASHA512}) + } + case *ecdsa.PublicKey: + // These are all the current defaults, but let's set them explicitly so + // golang.org/x/crypto/ssh can't change them in an update and break some + // HSM or KMS that wouldn't support the new default. + switch pub.Curve { + case elliptic.P256(): + return ssh.NewSignerWithAlgorithms(algorithmSigner, []string{ssh.KeyAlgoECDSA256}) + case elliptic.P384(): + return ssh.NewSignerWithAlgorithms(algorithmSigner, []string{ssh.KeyAlgoECDSA384}) + case elliptic.P521(): + return ssh.NewSignerWithAlgorithms(algorithmSigner, []string{ssh.KeyAlgoECDSA521}) + default: + return nil, trace.BadParameter("SSH CA: ECDSA curve: %s", pub.Curve.Params().Name) + } + case ed25519.PublicKey: + // This is the current default, but let's set it explicitly so + // golang.org/x/crypto/ssh can't change it in an update and break some + // HSM or KMS that wouldn't support the new default. + return ssh.NewSignerWithAlgorithms(algorithmSigner, []string{ssh.KeyAlgoED25519}) + default: + return nil, trace.BadParameter("SSH CA: unsupported key type: %s", sshSigner.PublicKey().Type()) } - return rsaSHA512Signer{MultiAlgorithmSigner: ss} -} - -type rsaSHA512Signer struct { - ssh.MultiAlgorithmSigner -} - -func (s rsaSHA512Signer) Algorithms() []string { - return []string{ssh.KeyAlgoRSASHA512} } // GetTLSCertAndSigner selects a usable TLS keypair from the given CA @@ -433,18 +468,16 @@ func (m *Manager) NewSSHKeyPair(ctx context.Context, purpose cryptosuites.KeyPur } func (m *Manager) newSSHKeyPair(ctx context.Context, alg cryptosuites.Algorithm) (*types.SSHKeyPair, error) { - // The default hash length for SSH signers is 512 bits. - sshKey, cryptoSigner, err := m.backendForNewKeys.generateKey(ctx, alg, withRSADigestAlgorithm(crypto.SHA512)) + sshKey, signer, err := m.backendForNewKeys.generateKey(ctx, alg) if err != nil { return nil, trace.Wrap(err) } - sshSigner, err := ssh.NewSignerFromSigner(cryptoSigner) + sshPub, err := ssh.NewPublicKey(signer.Public()) if err != nil { return nil, trace.Wrap(err) } - publicKey := ssh.MarshalAuthorizedKey(sshSigner.PublicKey()) return &types.SSHKeyPair{ - PublicKey: publicKey, + PublicKey: ssh.MarshalAuthorizedKey(sshPub), PrivateKey: sshKey, PrivateKeyType: keyType(sshKey), }, nil diff --git a/lib/auth/keystore/pkcs11.go b/lib/auth/keystore/pkcs11.go index 2eb546212f856..7cc8597138864 100644 --- a/lib/auth/keystore/pkcs11.go +++ b/lib/auth/keystore/pkcs11.go @@ -123,7 +123,7 @@ func (p *pkcs11KeyStore) findUnusedID() (keyID, error) { // generateKey creates a new private key and returns its identifier and a crypto.Signer. The returned // identifier can be passed to getSigner later to get an equivalent crypto.Signer. -func (p *pkcs11KeyStore) generateKey(ctx context.Context, alg cryptosuites.Algorithm, _ ...rsaKeyOption) ([]byte, crypto.Signer, error) { +func (p *pkcs11KeyStore) generateKey(ctx context.Context, alg cryptosuites.Algorithm) ([]byte, crypto.Signer, error) { // the key identifiers are not created in a thread safe // manner so all calls are serialized to prevent races. p.semaphore <- struct{}{} diff --git a/lib/auth/keystore/software.go b/lib/auth/keystore/software.go index 069ab04569fae..fff5e6dace9ac 100644 --- a/lib/auth/keystore/software.go +++ b/lib/auth/keystore/software.go @@ -59,7 +59,7 @@ func (s *softwareKeyStore) keyTypeDescription() string { // generateRSA creates a new private key and returns its identifier and a crypto.Signer. The returned // identifier for softwareKeyStore is a pem-encoded private key, and can be passed to getSigner later to get // an equivalent crypto.Signer. -func (s *softwareKeyStore) generateKey(ctx context.Context, alg cryptosuites.Algorithm, _ ...rsaKeyOption) ([]byte, crypto.Signer, error) { +func (s *softwareKeyStore) generateKey(ctx context.Context, alg cryptosuites.Algorithm) ([]byte, crypto.Signer, error) { if alg == cryptosuites.RSA2048 && s.rsaKeyPairSource != nil { privateKeyPEM, _, err := s.rsaKeyPairSource() if err != nil { diff --git a/lib/auth/keystore/ssh_signature_test.go b/lib/auth/keystore/ssh_signature_test.go new file mode 100644 index 0000000000000..772ccb92651a4 --- /dev/null +++ b/lib/auth/keystore/ssh_signature_test.go @@ -0,0 +1,151 @@ +// Teleport +// Copyright (C) 2024 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package keystore + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "testing" + "time" + + "github.com/stretchr/testify/require" + "golang.org/x/crypto/ssh" + + "github.com/gravitational/teleport/api/utils/keys" +) + +// TestSSHSignatureAlgorithm asserts that [ssh.(*Certificate).SignCert] honors +// the signature algorithm selected by [sshSignerFromCryptoSigner] and does not try to use +// a new signature format in a new release. +func TestSSHSignatureAlgorithm(t *testing.T) { + rsa2048Key, err := keys.ParsePrivateKey(testRSA2048PrivateKeyPEM) + require.NoError(t, err) + + rsa4096Key, err := keys.ParsePrivateKey(testRSA4096PrivateKeyPEM) + require.NoError(t, err) + + ecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + _, ed25519Key, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + + _, subjectKey, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + subjectPubKey, err := ssh.NewPublicKey(subjectKey.Public()) + require.NoError(t, err) + + for _, tc := range []struct { + desc string + signer crypto.Signer + expectSignatureFormat string + }{ + { + desc: "rsa2048", + signer: rsa2048Key, + expectSignatureFormat: ssh.KeyAlgoRSASHA256, + }, + { + desc: "rsa4096", + signer: rsa4096Key, + expectSignatureFormat: ssh.KeyAlgoRSASHA512, + }, + { + desc: "ecdsaP256", + signer: ecdsaKey, + expectSignatureFormat: ssh.KeyAlgoECDSA256, + }, + { + desc: "ed25519", + signer: ed25519Key, + expectSignatureFormat: ssh.KeyAlgoED25519, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + sshSigner, err := sshSignerFromCryptoSigner(tc.signer) + require.NoError(t, err) + cert := ssh.Certificate{ + Key: subjectPubKey, + ValidBefore: uint64(time.Now().Add(time.Hour).Unix()), + } + require.NoError(t, cert.SignCert(rand.Reader, sshSigner)) + require.Equal(t, tc.expectSignatureFormat, cert.Signature.Format) + + checker := ssh.CertChecker{ + IsUserAuthority: func(pub ssh.PublicKey) bool { + return pub == sshSigner.PublicKey() + }, + } + require.NoError(t, checker.CheckCert("root", &cert)) + }) + } +} + +var testRSA4096PrivateKeyPEM = []byte(`-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY-----`)