Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abstract out signature generation and verification to SignatureEnvelope #7

Merged
merged 1 commit into from
Jun 30, 2022
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: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module github.com/notaryproject/notation-core-go

go 1.17

require github.com/golang-jwt/jwt/v4 v4.4.1
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
32 changes: 31 additions & 1 deletion internal/crypto/hashutil/hash.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// Package hashutil provides utilities for hash.
package hashutil

import "crypto"
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
)

// ComputeHash computes the digest of the message with the given hash algorithm.
// Callers should check the availability of the hash algorithm before invoking.
Expand All @@ -13,3 +17,29 @@ func ComputeHash(hash crypto.Hash, message []byte) ([]byte, error) {
}
return h.Sum(nil), nil
}

// GetHasher picks up a recommended hashing algorithm for given public keys.
func GetHasher(pubKey crypto.PublicKey) (crypto.Hash, bool) {
var hash crypto.Hash
switch key := pubKey.(type) {
case *rsa.PublicKey:
switch key.Size() {
case 256:
hash = crypto.SHA256
case 384:
hash = crypto.SHA384
case 512:
hash = crypto.SHA512
}
case *ecdsa.PublicKey:
switch key.Curve.Params().BitSize {
case 256:
hash = crypto.SHA256
case 384:
hash = crypto.SHA384
case 521:
hash = crypto.SHA512
}
}
return hash, hash.Available()
}
149 changes: 149 additions & 0 deletions internal/testhelper/certificatetest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Package testhelper implements utility routines required for writing unit tests.
// The testhelper should only be used in unit tests.
package testhelper

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"math/big"
"time"
)

var (
rsaRoot CertTuple
rsaLeaf CertTuple
ecdsaRoot ECCertTuple
ecdsaLeaf ECCertTuple
unsupported CertTuple
)

type CertTuple struct {
Cert *x509.Certificate
PrivateKey *rsa.PrivateKey
}

type ECCertTuple struct {
Cert *x509.Certificate
PrivateKey *ecdsa.PrivateKey
}

// init runs before any other part of this package.
func init() {
setupCertificates()
}

// GetRSARootCertificate returns root certificate signed using RSA algorithm
func GetRSARootCertificate() CertTuple {
return rsaRoot
}

// GetRSALeafCertificate returns leaf certificate signed using RSA algorithm
func GetRSALeafCertificate() CertTuple {
return rsaLeaf
}

// GetECRootCertificate returns root certificate signed using EC algorithm
func GetECRootCertificate() ECCertTuple {
return ecdsaRoot
}

// GetECLeafCertificate returns leaf certificate signed using EC algorithm
func GetECLeafCertificate() ECCertTuple {
return ecdsaLeaf
}

// GetUnsupportedCertificate returns certificate signed using RSA algorithm with key size of 1024 bits
// which is not supported by notary.
func GetUnsupportedCertificate() CertTuple {
return unsupported
}
Comment on lines +39 to +63
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to define all these getters. Just use the global variables wherever necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having global gives less control than the function, because anyone can read or change the global variable at any time. Also, having function will allow us to change variable name/representation internally without affecting callers.


func setupCertificates() {
rsaRoot = getCertTuple("Notation Test Root", nil)
rsaLeaf = getCertTuple("Notation Test Leaf Cert", &rsaRoot)
ecdsaRoot = getECCertTuple("Notation Test Root2", nil)
ecdsaLeaf = getECCertTuple("Notation Test Leaf Cert", &ecdsaRoot)

// This will be flagged by the static code analyzer as 'Use of a weak cryptographic key' but its intentional
// and is used only for testing.
k, _ := rsa.GenerateKey(rand.Reader, 1024)
github-advanced-security[bot] marked this conversation as resolved.
Show resolved Hide resolved
unsupported = getRSACertTupleWithPK(k, "Notation Unsupported Root", nil)
}

func getCertTuple(cn string, issuer *CertTuple) CertTuple {
pk, _ := rsa.GenerateKey(rand.Reader, 3072)
return getRSACertTupleWithPK(pk, cn, issuer)
}

func getECCertTuple(cn string, issuer *ECCertTuple) ECCertTuple {
k, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
return getECDSACertTupleWithPK(k, cn, issuer)
}

func getRSACertTupleWithPK(privKey *rsa.PrivateKey, cn string, issuer *CertTuple) CertTuple {
template := getCertTemplate(issuer == nil, cn)

var certBytes []byte
if issuer != nil {
certBytes, _ = x509.CreateCertificate(rand.Reader, template, issuer.Cert, &privKey.PublicKey, issuer.PrivateKey)
} else {
certBytes, _ = x509.CreateCertificate(rand.Reader, template, template, &privKey.PublicKey, privKey)
}

cert, _ := x509.ParseCertificate(certBytes)
return CertTuple{
Cert: cert,
PrivateKey: privKey,
}
}

func getECDSACertTupleWithPK(privKey *ecdsa.PrivateKey, cn string, issuer *ECCertTuple) ECCertTuple {
template := getCertTemplate(issuer == nil, cn)

var certBytes []byte
if issuer != nil {
certBytes, _ = x509.CreateCertificate(rand.Reader, template, issuer.Cert, &privKey.PublicKey, issuer.PrivateKey)
} else {
certBytes, _ = x509.CreateCertificate(rand.Reader, template, template, &privKey.PublicKey, privKey)
}

cert, _ := x509.ParseCertificate(certBytes)
return ECCertTuple{
Cert: cert,
PrivateKey: privKey,
}
}

func getCertTemplate(isRoot bool, cn string) *x509.Certificate {
template := &x509.Certificate{
Subject: pkix.Name{
Organization: []string{"Notary"},
Country: []string{"US"},
Province: []string{"WA"},
Locality: []string{"Seattle"},
CommonName: cn,
},
NotBefore: time.Now(),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
}
if isRoot {
template.SerialNumber = big.NewInt(1)
template.NotAfter = time.Now().AddDate(0, 1, 0)
template.KeyUsage = x509.KeyUsageCertSign
template.BasicConstraintsValid = true
template.MaxPathLen = 1
template.IsCA = true
} else {
template.SerialNumber = big.NewInt(2)
template.NotAfter = time.Now().AddDate(0, 0, 1)
template.KeyUsage = x509.KeyUsageDigitalSignature
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}
}

return template
}
103 changes: 103 additions & 0 deletions signer/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package signer

import "fmt"

// SignatureIntegrityError is used when the Signature associated is no longer valid.
type SignatureIntegrityError struct {
priteshbandi marked this conversation as resolved.
Show resolved Hide resolved
err error
}

func (e SignatureIntegrityError) Error() string {
return fmt.Sprintf("signature is invalid. Error: %s", e.err.Error())
}

// MalformedSignatureError is used when Signature envelope is malformed.
type MalformedSignatureError struct {
msg string
}

func (e MalformedSignatureError) Error() string {
if e.msg != "" {
return e.msg
}
return "signature envelope format is malformed"

}

// UnsupportedSignatureFormatError is used when Signature envelope is not supported.
type UnsupportedSignatureFormatError struct {
mediaType string
}

func (e UnsupportedSignatureFormatError) Error() string {
return fmt.Sprintf("signature envelope format with media type %q is not supported", e.mediaType)
}

// SignatureNotFoundError is used when signature envelope is not present.
type SignatureNotFoundError struct{}

func (e SignatureNotFoundError) Error() string {
return "signature envelope is not present"
}

// SignatureAuthenticityError is used when signature is not generated using trusted certificates.
type SignatureAuthenticityError struct{}

func (e SignatureAuthenticityError) Error() string {
return "signature is not produced by a trusted signer"
}

// UnsupportedOperationError is used when an operation is not supported.
type UnsupportedOperationError struct {
operation string
}

func (e UnsupportedOperationError) Error() string {
return fmt.Sprintf("%q operation is not supported", e.operation)
}

// UnsupportedSigningKeyError is used when a signing key is not supported
type UnsupportedSigningKeyError struct {
keyType string
}

func (e UnsupportedSigningKeyError) Error() string {
if e.keyType != "" {
return fmt.Sprintf("%q signing key is not supported", e.keyType)
}
return "signing key is not supported"
}

// MalformedArgumentError is used when an argument to a function is malformed.
type MalformedArgumentError struct {
param string
err error
}

func (e MalformedArgumentError) Error() string {
if e.err != nil {
return fmt.Sprintf("%q param is malformed. Error: %s", e.param, e.err.Error())
}
return fmt.Sprintf("%q param is malformed", e.param)
}

// MalformedSignRequestError is used when SignRequest is malformed.
type MalformedSignRequestError struct {
msg string
}

func (e MalformedSignRequestError) Error() string {
if e.msg != "" {
return e.msg
}
return "SignRequest is malformed"
}

// SignatureAlgoNotSupportedError is used when signing algo is not supported.
type SignatureAlgoNotSupportedError struct {
alg string
}

func (e SignatureAlgoNotSupportedError) Error() string {
return fmt.Sprintf("signature algorithm %q is not supported", e.alg)
}
Loading