Move tests to use new slimmer controller test builder

Signed-off-by: JoshVanL <vleeuwenjoshua@gmail.com>
This commit is contained in:
JoshVanL 2019-07-26 13:47:27 +01:00
parent d98a6dc9d6
commit 6d3416325e
6 changed files with 156 additions and 266 deletions

View File

@ -16,13 +16,18 @@ limitations under the License.
package v1alpha1
// Annotation names for Secrets
const (
AltNamesAnnotationKey = "certmanager.k8s.io/alt-names"
IPSANAnnotationKey = "certmanager.k8s.io/ip-sans"
CommonNameAnnotationKey = "certmanager.k8s.io/common-name"
IssuerNameAnnotationKey = "certmanager.k8s.io/issuer-name"
IssuerKindAnnotationKey = "certmanager.k8s.io/issuer-kind"
CertificateNameKey = "certmanager.k8s.io/certificate-name"
)
// Annotation names for CertificateRequests
const (
AltNamesAnnotationKey = "certmanager.k8s.io/alt-names"
IPSANAnnotationKey = "certmanager.k8s.io/ip-sans"
CommonNameAnnotationKey = "certmanager.k8s.io/common-name"
IssuerNameAnnotationKey = "certmanager.k8s.io/issuer-name"
IssuerKindAnnotationKey = "certmanager.k8s.io/issuer-kind"
CertificateNameKey = "certmanager.k8s.io/certificate-name"
CRPrivateKeyAnnotationKey = "certmanager.k8s.io/private-key-secret-name"
)

View File

@ -14,7 +14,9 @@ go_library(
"//pkg/logs:go_default_library",
"//pkg/util/kube:go_default_library",
"//pkg/util/pki:go_default_library",
"//vendor/github.com/go-logr/logr:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/client-go/listers/core/v1:go_default_library",
"//vendor/k8s.io/client-go/tools/record:go_default_library",
],
@ -22,10 +24,7 @@ go_library(
go_test(
name = "go_default_test",
srcs = [
"selfsigned_test.go",
"util_test.go",
],
srcs = ["selfsigned_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/apis/certmanager/v1alpha1:go_default_library",

View File

@ -42,7 +42,7 @@ const (
)
var (
errorNoAnnotation = fmt.Errorf("self signed issuer requires '%s' annotation set to secret name holding the private key",
errorNoAnnotation = fmt.Errorf("self signed issuer requires %q annotation to be set to the name of the Secret containing the private key",
v1alpha1.CRPrivateKeyAnnotationKey)
)
@ -80,22 +80,25 @@ func (s *SelfSigned) Sign(ctx context.Context, cr *v1alpha1.CertificateRequest)
skRef, ok := cr.ObjectMeta.Annotations[v1alpha1.CRPrivateKeyAnnotationKey]
if !ok || skRef == "" {
s.reportFaliedStatus(log, cr, errorNoAnnotation, "ErrorAnnotation",
fmt.Sprintf("Referenced secret %s/%s not found", cr.Namespace, skRef))
s.reportPendingStatus(log, cr, errorNoAnnotation, "MissingAnnotation",
fmt.Sprintf("Annotation %q missing or reference empty",
v1alpha1.CRPrivateKeyAnnotationKey))
return nil, nil
}
sk, err := kube.SecretTLSKey(ctx, s.secretsLister, cr.Namespace, skRef)
privatekey, err := kube.SecretTLSKey(ctx, s.secretsLister, cr.Namespace, skRef)
if k8sErrors.IsNotFound(err) {
s.reportFaliedStatus(log, cr, err, "ErrorSecret",
s.reportPendingStatus(log, cr, err, "MissingSecret",
fmt.Sprintf("Referenced secret %s/%s not found", cr.Namespace, skRef))
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("failed to get private key %s referenced in the annotation '%s': %s",
skRef, v1alpha1.CRPrivateKeyAnnotationKey, err)
s.reportFaliedStatus(log, cr, err, "ErrorGettingKey",
fmt.Sprintf("Failed to get key %q referenced in annotation %q",
skRef, v1alpha1.CRPrivateKeyAnnotationKey))
return nil, nil
}
template, err := pki.GenerateTemplateFromCertificateRequest(cr)
@ -105,13 +108,13 @@ func (s *SelfSigned) Sign(ctx context.Context, cr *v1alpha1.CertificateRequest)
}
// extract the public component of the key
pk, err := pki.PublicKeyForPrivateKey(sk)
publickey, err := pki.PublicKeyForPrivateKey(privatekey)
if err != nil {
s.reportFaliedStatus(log, cr, err, "ErrorPublicKey", "Failed to get public key from private key")
return nil, nil
}
ok, err = pki.PublicKeyMatchesPublicKey(pk, template.PublicKey)
ok, err = pki.PublicKeysEqual(publickey, template.PublicKey)
if err != nil || !ok {
if err == nil {
@ -124,14 +127,15 @@ func (s *SelfSigned) Sign(ctx context.Context, cr *v1alpha1.CertificateRequest)
}
// sign and encode the certificate
certPem, _, err := pki.SignCertificate(template, template, pk, sk)
certPem, _, err := pki.SignCertificate(template, template, publickey, privatekey)
if err != nil {
s.reportFaliedStatus(log, cr, err, "ErrorSigning", "Error signing certificate")
return nil, err
return nil, nil
}
log.Info("self signed certificate issued")
// We set the CA to the returned certificate here since this is self signed.
return &issuer.IssueResponse{
Certificate: certPem,
CA: certPem,
@ -140,11 +144,22 @@ func (s *SelfSigned) Sign(ctx context.Context, cr *v1alpha1.CertificateRequest)
func (s *SelfSigned) reportFaliedStatus(log logr.Logger, cr *v1alpha1.CertificateRequest, err error,
reason, message string) {
s.recorder.Event(cr, corev1.EventTypeWarning, reason, fmt.Sprintf("%s: %v", message, err))
s.reportStatus(log, cr, err, reason, message, v1alpha1.ConditionFalse, v1alpha1.CertificateRequestReasonFailed)
}
func (s *SelfSigned) reportPendingStatus(log logr.Logger, cr *v1alpha1.CertificateRequest, err error,
reason, message string) {
s.recorder.Event(cr, corev1.EventTypeNormal, reason, fmt.Sprintf("%s: %v", message, err))
s.reportStatus(log, cr, err, reason, message, v1alpha1.ConditionFalse, v1alpha1.CertificateRequestReasonPending)
}
func (s *SelfSigned) reportStatus(log logr.Logger, cr *v1alpha1.CertificateRequest, err error,
reason, message string, condtion v1alpha1.ConditionStatus, reasonMessage v1alpha1.CertificateConditionType) {
log.Error(err, message)
// TODO: add mechanism here to handle invalid input errors which should result in a permanent failure
apiutil.SetCertificateRequestCondition(cr, v1alpha1.CertificateRequestConditionReady,
v1alpha1.ConditionFalse, v1alpha1.CertificateRequestReasonFailed,
message)
log.Error(err, message)
s.recorder.Event(cr, corev1.EventTypeWarning, reason, fmt.Sprintf("%s: %v", message, err))
}

View File

@ -18,6 +18,7 @@ package selfsigned
import (
"bytes"
"context"
"crypto"
"crypto/rand"
"crypto/x509"
@ -57,10 +58,10 @@ func generateCSR(t *testing.T, secretKey crypto.Signer, alg x509.SignatureAlgori
return csr
}
func mustNoResponse(t *testing.T, args []interface{}) {
resp := args[1].(*issuer.IssueResponse)
func mustNoResponse(builder *testpkg.Builder, args ...interface{}) {
resp := args[0].(*issuer.IssueResponse)
if resp != nil {
t.Errorf("unexpected response, exp='nil' got='%+v'", resp)
builder.T.Errorf("unexpected response, exp='nil' got='%+v'", resp)
}
}
@ -131,217 +132,185 @@ func TestSign(t *testing.T) {
},
}
tests := map[string]selfsignedFixture{
"a CertificateRequest with no certmanager.k8s.io/selfsigned-private-key annotation should record error": {
Issuer: gen.Issuer("selfsigned-issuer",
gen.SetIssuerSelfSigned(v1alpha1.SelfSignedIssuer{}),
),
// no data in annotation
CertificateRequest: gen.CertificateRequest("test-cr"),
Builder: &testpkg.Builder{
KubeObjects: []runtime.Object{},
CertManagerObjects: []runtime.Object{},
tests := map[string]testT{
"a CertificateRequest with no certmanager.k8s.io/selfsigned-private-key annotation should record pending": {
// no annotation
certificaterequest: gen.CertificateRequest("test-cr"),
builder: &testpkg.Builder{
ExpectedEvents: []string{
"Warning ErrorAnnotation Referenced secret default-unit-test-ns/ not found: self signed issuer requires 'certmanager.k8s.io/private-key-secret-name' annotation set to secret name holding the private key",
`Normal MissingAnnotation Annotation "certmanager.k8s.io/private-key-secret-name" missing or reference empty: self signed issuer requires "certmanager.k8s.io/private-key-secret-name" annotation to be set to the name of the Secret containing the private key`,
},
CheckFn: mustNoResponse,
},
Err: false,
CheckFn: func(t *testing.T, s *selfsignedFixture, args ...interface{}) {
mustNoResponse(t, args)
},
expectedErr: false,
},
"a CertificateRequest with a certmanager.k8s.io/private-key-secret-name annotation but empty string should record error": {
Issuer: gen.Issuer("selfsigned-issuer",
gen.SetIssuerSelfSigned(v1alpha1.SelfSignedIssuer{}),
),
"a CertificateRequest with a certmanager.k8s.io/private-key-secret-name annotation but empty string should record pending": {
// no data in annotation
CertificateRequest: gen.CertificateRequest("test-cr",
certificaterequest: gen.CertificateRequest("test-cr",
gen.AddCertificateRequestAnnotations(map[string]string{
v1alpha1.CRPrivateKeyAnnotationKey: "",
}),
),
Builder: &testpkg.Builder{
KubeObjects: []runtime.Object{},
CertManagerObjects: []runtime.Object{},
builder: &testpkg.Builder{
ExpectedEvents: []string{
"Warning ErrorAnnotation Referenced secret default-unit-test-ns/ not found: self signed issuer requires 'certmanager.k8s.io/private-key-secret-name' annotation set to secret name holding the private key",
`Normal MissingAnnotation Annotation "certmanager.k8s.io/private-key-secret-name" missing or reference empty: self signed issuer requires "certmanager.k8s.io/private-key-secret-name" annotation to be set to the name of the Secret containing the private key`,
},
CheckFn: mustNoResponse,
},
Err: false,
CheckFn: func(t *testing.T, s *selfsignedFixture, args ...interface{}) {
},
expectedErr: false,
},
"a CertificateRequest with a certmanager.k8s.io/private-key-secret-name annotation but the referenced secret doesn't exist should record error": {
Issuer: gen.Issuer("selfsigned-issuer",
gen.SetIssuerSelfSigned(v1alpha1.SelfSignedIssuer{}),
),
CertificateRequest: gen.CertificateRequest("test-cr",
"a CertificateRequest with a certmanager.k8s.io/private-key-secret-name annotation but the referenced secret doesn't exist should record pending": {
certificaterequest: gen.CertificateRequest("test-cr",
gen.AddCertificateRequestAnnotations(map[string]string{
v1alpha1.CRPrivateKeyAnnotationKey: "a-non-existent-secret",
}),
),
Builder: &testpkg.Builder{
KubeObjects: []runtime.Object{},
CertManagerObjects: []runtime.Object{},
builder: &testpkg.Builder{
ExpectedEvents: []string{
`Warning ErrorSecret Referenced secret default-unit-test-ns/a-non-existent-secret not found: secret "a-non-existent-secret" not found`,
`Normal MissingSecret Referenced secret default-unit-test-ns/a-non-existent-secret not found: secret "a-non-existent-secret" not found`,
},
CheckFn: mustNoResponse,
},
Err: false,
CheckFn: func(t *testing.T, s *selfsignedFixture, args ...interface{}) {
},
expectedErr: false,
},
"a CertificateRequest with a bad CSR should error": {
Issuer: gen.Issuer("selfsigned-issuer",
gen.SetIssuerSelfSigned(v1alpha1.SelfSignedIssuer{}),
),
CertificateRequest: gen.CertificateRequest("test-cr",
"a CertificateRequest with a bad CSR should fail": {
certificaterequest: gen.CertificateRequest("test-cr",
gen.AddCertificateRequestAnnotations(map[string]string{
v1alpha1.CRPrivateKeyAnnotationKey: "test-rsa-key",
}),
gen.SetCertificateRequestCSR([]byte("this is a bad CSR")),
),
Builder: &testpkg.Builder{
KubeObjects: []runtime.Object{rsaKeySecret},
CertManagerObjects: []runtime.Object{},
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{rsaKeySecret},
ExpectedEvents: []string{
"Warning ErrorGenerating Failed to generate certificate template: failed to decode csr from certificate request resource default-unit-test-ns/test-cr",
},
CheckFn: mustNoResponse,
},
Err: false,
CheckFn: func(t *testing.T, s *selfsignedFixture, args ...interface{}) {
},
expectedErr: false,
},
"a CertificateRequest referencing a bad key should record an error": {
Issuer: gen.Issuer("selfsigned-issuer",
gen.SetIssuerSelfSigned(v1alpha1.SelfSignedIssuer{}),
),
CertificateRequest: gen.CertificateRequest("test-cr",
"a CertificateRequest referencing a bad key should fail": {
certificaterequest: gen.CertificateRequest("test-cr",
gen.AddCertificateRequestAnnotations(map[string]string{
v1alpha1.CRPrivateKeyAnnotationKey: "test-bad-key",
}),
gen.SetCertificateRequestCSR(csrBytes),
),
Builder: &testpkg.Builder{
KubeObjects: []runtime.Object{badKeySecret},
CertManagerObjects: []runtime.Object{},
},
Err: true,
CheckFn: func(t *testing.T, s *selfsignedFixture, args ...interface{}) {
badKeyError := "failed to get private key test-bad-key referenced in the annotation 'certmanager.k8s.io/private-key-secret-name': error decoding private key PEM block"
err := args[2].(error)
if err == nil || err.Error() != badKeyError {
t.Errorf("unexpected error, exp='%s' got='%+v'", badKeyError, err)
}
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{badKeySecret},
CheckFn: mustNoResponse,
ExpectedEvents: []string{
`Warning ErrorGettingKey Failed to get key "test-bad-key" referenced in annotation "certmanager.k8s.io/private-key-secret-name": error decoding private key PEM block`,
},
},
expectedErr: false,
},
"a CertificateRequest referencing a key which did not sign the CSR should record an error": {
Issuer: gen.Issuer("selfsigned-issuer",
gen.SetIssuerSelfSigned(v1alpha1.SelfSignedIssuer{}),
),
CertificateRequest: gen.CertificateRequest("test-cr",
"a CertificateRequest referencing a key which did not sign the CSR should fail": {
certificaterequest: gen.CertificateRequest("test-cr",
gen.AddCertificateRequestAnnotations(map[string]string{
v1alpha1.CRPrivateKeyAnnotationKey: "test-another-rsa-key",
}),
gen.SetCertificateRequestCSR(csrBytes),
),
Builder: &testpkg.Builder{
KubeObjects: []runtime.Object{anotherRSAKeySecret},
CertManagerObjects: []runtime.Object{},
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{anotherRSAKeySecret},
ExpectedEvents: []string{
"Warning ErrorKeyMatch Error generating certificate template: CSR not signed by referenced private key",
},
CheckFn: mustNoResponse,
},
Err: false,
CheckFn: func(t *testing.T, s *selfsignedFixture, args ...interface{}) {
},
expectedErr: false,
},
"a valid RSA key should sign a self signed certificate": {
Issuer: gen.Issuer("selfsigned-issuer",
gen.SetIssuerSelfSigned(v1alpha1.SelfSignedIssuer{}),
),
CertificateRequest: gen.CertificateRequest("test-cr",
certificaterequest: gen.CertificateRequest("test-cr",
gen.AddCertificateRequestAnnotations(map[string]string{
v1alpha1.CRPrivateKeyAnnotationKey: "test-rsa-key",
}),
gen.SetCertificateRequestCSR(csrBytes),
),
Builder: &testpkg.Builder{
KubeObjects: []runtime.Object{rsaKeySecret},
CertManagerObjects: []runtime.Object{},
},
Err: false,
CheckFn: func(t *testing.T, s *selfsignedFixture, args ...interface{}) {
resp := args[1].(*issuer.IssueResponse)
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{rsaKeySecret},
CheckFn: func(builder *testpkg.Builder, args ...interface{}) {
resp := args[0].(*issuer.IssueResponse)
// CA and cert should be the same.
if !bytes.Equal(resp.CA, resp.Certificate) {
t.Errorf("expected CA and cert to be the same but got:\nCA: %s\nCert: %s",
resp.CA, resp.Certificate)
}
// CA and cert should be the same.
if !bytes.Equal(resp.CA, resp.Certificate) {
t.Errorf("expected CA and cert to be the same but got:\nCA: %s\nCert: %s",
resp.CA, resp.Certificate)
}
// No private key should be returned.
if len(resp.PrivateKey) > 0 {
t.Errorf("expected to private key returned but got: %s",
resp.PrivateKey)
}
// No private key should be returned.
if len(resp.PrivateKey) > 0 {
t.Errorf("expected to private key returned but got: %s",
resp.PrivateKey)
}
},
},
expectedErr: false,
},
"a valid ECDSA key should sign a self signed certificate": {
Issuer: gen.Issuer("selfsigned-issuer",
gen.SetIssuerSelfSigned(v1alpha1.SelfSignedIssuer{}),
),
CertificateRequest: gen.CertificateRequest("test-cr",
certificaterequest: gen.CertificateRequest("test-cr",
gen.AddCertificateRequestAnnotations(map[string]string{
v1alpha1.CRPrivateKeyAnnotationKey: "test-ecdsa-key",
}),
gen.SetCertificateRequestCSR(csrECBytes),
),
Builder: &testpkg.Builder{
KubeObjects: []runtime.Object{ecKeySecret},
CertManagerObjects: []runtime.Object{},
},
Err: false,
CheckFn: func(t *testing.T, s *selfsignedFixture, args ...interface{}) {
resp := args[1].(*issuer.IssueResponse)
if resp == nil {
t.Errorf("expected a response but got: %+v",
args[1])
return
}
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{ecKeySecret},
CheckFn: func(builder *testpkg.Builder, args ...interface{}) {
resp := args[0].(*issuer.IssueResponse)
if resp == nil {
t.Errorf("expected a response but got: %+v",
args[1])
return
}
// CA and cert should be the same.
if !bytes.Equal(resp.CA, resp.Certificate) {
t.Errorf("expected CA and cert to be the same but got:\nCA: %s\nCert: %s",
resp.CA, resp.Certificate)
}
// CA and cert should be the same.
if !bytes.Equal(resp.CA, resp.Certificate) {
t.Errorf("expected CA and cert to be the same but got:\nCA: %s\nCert: %s",
resp.CA, resp.Certificate)
}
// No private key should be returned.
if len(resp.PrivateKey) > 0 {
t.Errorf("expected to private key returned but got: %s",
resp.PrivateKey)
}
// No private key should be returned.
if len(resp.PrivateKey) > 0 {
t.Errorf("expected to private key returned but got: %s",
resp.PrivateKey)
}
},
},
expectedErr: false,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
if test.Builder == nil {
test.Builder = &testpkg.Builder{}
}
test.Setup(t)
crCopy := test.CertificateRequest.DeepCopy()
resp, err := test.SelfSigned.Sign(test.Ctx, crCopy)
if err != nil && !test.Err {
t.Errorf("Expected function to not error, but got: %v", err)
}
if err == nil && test.Err {
t.Errorf("Expected function to get an error, but got: %v", err)
}
test.Finish(t, crCopy, resp, err)
runTest(t, test)
})
}
}
type testT struct {
builder *testpkg.Builder
certificaterequest *v1alpha1.CertificateRequest
checkFn func(*testpkg.Builder, ...interface{})
expectedErr bool
}
func runTest(t *testing.T, test testT) {
test.builder.T = t
test.builder.Start()
defer test.builder.Stop()
c := NewSelfSigned(test.builder.Context)
test.builder.Sync()
resp, err := c.Sign(context.Background(), test.certificaterequest)
if err != nil && !test.expectedErr {
t.Errorf("expected to not get an error, but got: %v", err)
}
if err == nil && test.expectedErr {
t.Errorf("expected to get an error but did not get one")
}
test.builder.CheckAndFinish(resp, err)
}

View File

@ -1,98 +0,0 @@
/*
Copyright 2019 The Jetstack cert-manager contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package selfsigned
import (
"context"
"testing"
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
"github.com/jetstack/cert-manager/pkg/controller/test"
)
type selfsignedFixture struct {
SelfSigned *SelfSigned
*test.Builder
Issuer v1alpha1.GenericIssuer
Certificate *v1alpha1.Certificate
CertificateRequest *v1alpha1.CertificateRequest
PreFn func(*testing.T, *selfsignedFixture)
CheckFn func(*testing.T, *selfsignedFixture, ...interface{})
Err bool
Ctx context.Context
}
func (s *selfsignedFixture) Setup(t *testing.T) {
if s.Issuer == nil {
s.Issuer = &v1alpha1.Issuer{
Spec: v1alpha1.IssuerSpec{
IssuerConfig: v1alpha1.IssuerConfig{
SelfSigned: &v1alpha1.SelfSignedIssuer{},
},
},
}
}
if s.Ctx == nil {
s.Ctx = context.Background()
}
if s.Builder == nil {
// TODO: set default IssuerOptions
// defaultTestAcmeClusterResourceNamespace,
// defaultTestSolverImage,
// default dns01 nameservers
// ambient credentials settings
s.Builder = &test.Builder{}
}
if s.Builder.T == nil {
s.Builder.T = t
}
s.SelfSigned = s.buildFakeSelfSigned(s.Builder, s.Issuer)
if s.PreFn != nil {
s.PreFn(t, s)
s.Builder.Sync()
}
}
func (s *selfsignedFixture) Finish(t *testing.T, args ...interface{}) {
defer s.Builder.Stop()
if err := s.Builder.AllReactorsCalled(); err != nil {
t.Errorf("Not all expected reactors were called: %v", err)
}
if err := s.Builder.AllActionsExecuted(); err != nil {
t.Errorf(err.Error())
}
if err := s.Builder.AllEventsCalled(); err != nil {
t.Errorf(err.Error())
}
// resync listers before running checks
s.Builder.Sync()
// run custom checks
if s.CheckFn != nil {
s.CheckFn(t, s, args...)
}
}
func (s *selfsignedFixture) buildFakeSelfSigned(b *test.Builder, issuer v1alpha1.GenericIssuer) *SelfSigned {
b.Start()
selfsignedStruct := NewSelfSigned(b.Context)
b.Sync()
return selfsignedStruct
}

View File

@ -210,10 +210,10 @@ func PublicKeyMatchesCertificate(check crypto.PublicKey, crt *x509.Certificate)
// It will return an error if either of the passed parameters are of an
// unrecognised type (i.e. non RSA/ECDSA)
func PublicKeyMatchesCSR(check crypto.PublicKey, csr *x509.CertificateRequest) (bool, error) {
return PublicKeyMatchesPublicKey(check, csr.PublicKey)
return PublicKeysEqual(check, csr.PublicKey)
}
func PublicKeyMatchesPublicKey(a, b crypto.PublicKey) (bool, error) {
func PublicKeysEqual(a, b crypto.PublicKey) (bool, error) {
switch pub := a.(type) {
case *rsa.PublicKey:
rsaCheck, ok := b.(*rsa.PublicKey)