From 6959f8120b1e01e501852bebcfdd5a523c60433a Mon Sep 17 00:00:00 2001 From: Johannes Schicktanz Date: Tue, 22 Mar 2022 16:07:22 +0100 Subject: [PATCH] enhance signatures (#58) * adds media type to signatures * fix tests * review feedback * removes omitempty from media type --- bindings-go/apis/v2/componentdescriptor.go | 22 ++++- bindings-go/apis/v2/signatures/rsa.go | 89 ++++++++++++++++--- bindings-go/apis/v2/signatures/rsa_test.go | 6 +- bindings-python/gci/componentmodel.py | 1 + .../component-descriptor-v2-schema.yaml | 4 + 5 files changed, 103 insertions(+), 19 deletions(-) diff --git a/bindings-go/apis/v2/componentdescriptor.go b/bindings-go/apis/v2/componentdescriptor.go index 42b40cac..64043a38 100644 --- a/bindings-go/apis/v2/componentdescriptor.go +++ b/bindings-go/apis/v2/componentdescriptor.go @@ -438,7 +438,7 @@ func (o *ComponentReference) GetIdentityDigest() []byte { return o.GetIdentity().Digest() } -// DigestSpec defines the digest and algorithm. +// DigestSpec defines a digest. // +k8s:deepcopy-gen=true // +k8s:openapi-gen=true type DigestSpec struct { @@ -447,14 +447,32 @@ type DigestSpec struct { Value string `json:"value"` } -// SignatureSpec defines the signature and algorithm. +// SignatureSpec defines a signature. // +k8s:deepcopy-gen=true // +k8s:openapi-gen=true type SignatureSpec struct { Algorithm string `json:"algorithm"` Value string `json:"value"` + MediaType string `json:"mediaType"` } +const ( + // SignaturePEMBlockType defines the type of a signature pem block. + SignaturePEMBlockType = "SIGNATURE" + + // SignaturePEMBlockAlgorithmHeader defines the header in a signature pem block where the signature algorithm is defined. + SignaturePEMBlockAlgorithmHeader = "Algorithm" + + // MediaTypePEM defines the media type for PEM formatted data. + MediaTypePEM = "application/x-pem-file" + + // MediaTypeHexEncodedRSASignature defines the media type for a plain, hex encoded RSA signature. + MediaTypeHexEncodedRSASignature = "application/vnd.ocm.signature.rsa+hex" + + // SignatureAlgorithmRSA defines the type for the RSA PKCS #1 v1.5 signature algorithm + SignatureAlgorithmRSAPKCS1v15 = "RSASSA-PKCS1-V1_5" +) + // NormalisationAlgorithm types and versions the algorithm used for digest generation. type NormalisationAlgorithm string diff --git a/bindings-go/apis/v2/signatures/rsa.go b/bindings-go/apis/v2/signatures/rsa.go index 4315ef94..ccc66594 100644 --- a/bindings-go/apis/v2/signatures/rsa.go +++ b/bindings-go/apis/v2/signatures/rsa.go @@ -6,6 +6,7 @@ import ( "crypto/x509" "encoding/hex" "encoding/pem" + "errors" "fmt" "io/ioutil" "strings" @@ -13,7 +14,7 @@ import ( v2 "github.com/gardener/component-spec/bindings-go/apis/v2" ) -// RsaSigner is a signatures.Signer compatible struct to sign with RSASSA-PKCS1-V1_5-SIGN. +// RsaSigner is a signatures.Signer compatible struct to sign with RSASSA-PKCS1-V1_5. type RsaSigner struct { privateKey rsa.PrivateKey } @@ -30,10 +31,16 @@ func CreateRsaSignerFromKeyFile(pathToPrivateKey string) (*RsaSigner, error) { if block == nil { return nil, fmt.Errorf("failed decoding PEM formatted block in key %w", err) } - key, err := x509.ParsePKCS1PrivateKey(block.Bytes) + untypedPrivateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { return nil, fmt.Errorf("failed parsing key %w", err) } + + key, ok := untypedPrivateKey.(*rsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("parsed key is not of type *rsa.PrivateKey: %T", untypedPrivateKey) + } + return &RsaSigner{ privateKey: *key, }, nil @@ -54,8 +61,9 @@ func (s RsaSigner) Sign(componentDescriptor v2.ComponentDescriptor, digest v2.Di return nil, fmt.Errorf("failed signing hash, %w", err) } return &v2.SignatureSpec{ - Algorithm: "RSASSA-PKCS1-V1_5-SIGN", + Algorithm: v2.SignatureAlgorithmRSAPKCS1v15, Value: hex.EncodeToString(signature), + MediaType: v2.MediaTypeHexEncodedRSASignature, }, nil } @@ -68,12 +76,25 @@ func hashAlgorithmLookup(algorithm string) (crypto.Hash, error) { return 0, fmt.Errorf("hash Algorithm %s not found", algorithm) } -// RsaVerifier is a signatures.Verifier compatible struct to verify RSASSA-PKCS1-V1_5-SIGN signatures. +// RsaVerifier is a signatures.Verifier compatible struct to verify RSASSA-PKCS1-V1_5 signatures. type RsaVerifier struct { publicKey rsa.PublicKey } -// CreateRsaVerifierFromKeyFile creates an Instance of RsaVerifier with the given rsa public key. +// CreateRsaVerifier creates an instance of RsaVerifier from a given rsa public key. +func CreateRsaVerifier(publicKey *rsa.PublicKey) (*RsaVerifier, error) { + if publicKey == nil { + return nil, errors.New("public key must not be nil") + } + + verifier := RsaVerifier{ + publicKey: *publicKey, + } + + return &verifier, nil +} + +// CreateRsaVerifierFromKeyFile creates an instance of RsaVerifier from a rsa public key file. // The private key has to be in the PKIX, ASN.1 DER form, see x509.ParsePKIXPublicKey. func CreateRsaVerifierFromKeyFile(pathToPublicKey string) (*RsaVerifier, error) { publicKey, err := ioutil.ReadFile(pathToPublicKey) @@ -90,9 +111,7 @@ func CreateRsaVerifierFromKeyFile(pathToPublicKey string) (*RsaVerifier, error) } switch key := untypedKey.(type) { case *rsa.PublicKey: - return &RsaVerifier{ - publicKey: *key, - }, nil + return CreateRsaVerifier(key) default: return nil, fmt.Errorf("public key format is not supported. Only rsa.PublicKey is supported") } @@ -100,11 +119,28 @@ func CreateRsaVerifierFromKeyFile(pathToPublicKey string) (*RsaVerifier, error) // Verify checks the signature, returns an error on verification failure func (v RsaVerifier) Verify(componentDescriptor v2.ComponentDescriptor, signature v2.Signature) error { - decodedHash, err := hex.DecodeString(signature.Digest.Value) - if err != nil { - return fmt.Errorf("failed decoding hash %s: %w", signature.Digest.Value, err) + var signatureBytes []byte + var err error + switch signature.Signature.MediaType { + case v2.MediaTypeHexEncodedRSASignature: + signatureBytes, err = hex.DecodeString(signature.Signature.Value) + if err != nil { + return fmt.Errorf("unable to get signature value: failed decoding hash %s: %w", signature.Digest.Value, err) + } + case v2.MediaTypePEM: + signaturePemBlocks, err := GetSignaturePEMBlocks([]byte(signature.Signature.Value)) + if err != nil { + return fmt.Errorf("unable to get signature pem blocks: %w", err) + } + if len(signaturePemBlocks) != 1 { + return fmt.Errorf("expected 1 signature pem block, found %d", len(signaturePemBlocks)) + } + signatureBytes = signaturePemBlocks[0].Bytes + default: + return fmt.Errorf("invalid signature mediaType %s", signature.Signature.MediaType) } - decodedSignature, err := hex.DecodeString(signature.Signature.Value) + + decodedHash, err := hex.DecodeString(signature.Digest.Value) if err != nil { return fmt.Errorf("failed decoding hash %s: %w", signature.Digest.Value, err) } @@ -112,9 +148,34 @@ func (v RsaVerifier) Verify(componentDescriptor v2.ComponentDescriptor, signatur if err != nil { return fmt.Errorf("failed looking up hash algorithm for %s: %w", signature.Digest.HashAlgorithm, err) } - err = rsa.VerifyPKCS1v15(&v.publicKey, algorithm, decodedHash, decodedSignature) - if err != nil { + if err := rsa.VerifyPKCS1v15(&v.publicKey, algorithm, decodedHash, signatureBytes); err != nil { return fmt.Errorf("signature verification failed, %w", err) } return nil } + +// GetSignaturePEMBlocks returns all signature pem blocks from a list of pem blocks +func GetSignaturePEMBlocks(pemData []byte) ([]*pem.Block, error) { + if len(pemData) == 0 { + return []*pem.Block{}, nil + } + + signatureBlocks := []*pem.Block{} + for { + var currentBlock *pem.Block + currentBlock, pemData = pem.Decode(pemData) + if currentBlock == nil && len(pemData) > 0 { + return nil, fmt.Errorf("unable to decode pem block %s", string(pemData)) + } + + if currentBlock.Type == v2.SignaturePEMBlockType { + signatureBlocks = append(signatureBlocks, currentBlock) + } + + if len(pemData) == 0 { + break + } + } + + return signatureBlocks, nil +} diff --git a/bindings-go/apis/v2/signatures/rsa_test.go b/bindings-go/apis/v2/signatures/rsa_test.go index 53617555..0a11e735 100644 --- a/bindings-go/apis/v2/signatures/rsa_test.go +++ b/bindings-go/apis/v2/signatures/rsa_test.go @@ -57,7 +57,7 @@ var _ = Describe("RSA Sign/Verify", func() { Value: hex.EncodeToString(hashOfString[:]), }) Expect(err).To(BeNil()) - Expect(signature.Algorithm).To(BeIdenticalTo("RSASSA-PKCS1-V1_5-SIGN")) + Expect(signature.Algorithm).To(BeIdenticalTo(v2.SignatureAlgorithmRSAPKCS1v15)) Expect(signature.Value).NotTo(BeNil()) }) It("should should fail on unknown Digest algorithm", func() { @@ -88,7 +88,7 @@ var _ = Describe("RSA Sign/Verify", func() { } signature, err := signer.Sign(v2.ComponentDescriptor{}, digest) Expect(err).To(BeNil()) - Expect(signature.Algorithm).To(BeIdenticalTo("RSASSA-PKCS1-V1_5-SIGN")) + Expect(signature.Algorithm).To(BeIdenticalTo(v2.SignatureAlgorithmRSAPKCS1v15)) Expect(signature.Value).NotTo(BeNil()) verifier, err := signatures.CreateRsaVerifierFromKeyFile(pathPublicKey) @@ -117,7 +117,7 @@ var _ = Describe("RSA Sign/Verify", func() { } signature, err := signer.Sign(v2.ComponentDescriptor{}, digest) Expect(err).To(BeNil()) - Expect(signature.Algorithm).To(BeIdenticalTo("RSASSA-PKCS1-V1_5-SIGN")) + Expect(signature.Algorithm).To(BeIdenticalTo(v2.SignatureAlgorithmRSAPKCS1v15)) Expect(signature.Value).NotTo(BeNil()) verifier, err := signatures.CreateRsaVerifierFromKeyFile(pathPublicKey) diff --git a/bindings-python/gci/componentmodel.py b/bindings-python/gci/componentmodel.py index 21e11f6e..6ff758a2 100644 --- a/bindings-python/gci/componentmodel.py +++ b/bindings-python/gci/componentmodel.py @@ -181,6 +181,7 @@ class DigestSpec: class SignatureSpec: algorithm: str value: str + mediaType: str @dc class Signature: diff --git a/language-independent/component-descriptor-v2-schema.yaml b/language-independent/component-descriptor-v2-schema.yaml index d4333b8a..9e88424a 100644 --- a/language-independent/component-descriptor-v2-schema.yaml +++ b/language-independent/component-descriptor-v2-schema.yaml @@ -147,11 +147,15 @@ definitions: required: - algorithm - value + - mediaType properties: algorithm: type: string value: type: string + mediaType: + description: 'The media type of the signature value' + type: string signature: type: 'object'