Skip to content
This repository has been archived by the owner on Feb 12, 2024. It is now read-only.

Commit

Permalink
enhance signatures (#58)
Browse files Browse the repository at this point in the history
* adds media type to signatures

* fix tests

* review feedback

* removes omitempty from media type
  • Loading branch information
jschicktanz authored Mar 22, 2022
1 parent 5635b1c commit 6959f81
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 19 deletions.
22 changes: 20 additions & 2 deletions bindings-go/apis/v2/componentdescriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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

Expand Down
89 changes: 75 additions & 14 deletions bindings-go/apis/v2/signatures/rsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import (
"crypto/x509"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"strings"

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
}
Expand All @@ -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
Expand All @@ -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
}

Expand All @@ -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)
Expand All @@ -90,31 +111,71 @@ 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")
}
}

// 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)
}
algorithm, err := hashAlgorithmLookup(signature.Digest.HashAlgorithm)
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
}
6 changes: 3 additions & 3 deletions bindings-go/apis/v2/signatures/rsa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions bindings-python/gci/componentmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ class DigestSpec:
class SignatureSpec:
algorithm: str
value: str
mediaType: str

@dc
class Signature:
Expand Down
4 changes: 4 additions & 0 deletions language-independent/component-descriptor-v2-schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down

0 comments on commit 6959f81

Please sign in to comment.