Skip to content

Commit

Permalink
feat: add support for additional transparency log key types (#197)
Browse files Browse the repository at this point in the history
* feat: add support for additional transparency log key types

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* feat: add unit tests

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: revert signature hash func update

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: ignore deprecated warning in linter

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: changes from review

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

---------

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>
  • Loading branch information
vishal-chdhry authored Jul 29, 2024
1 parent 2b6fc6d commit 40042e6
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 16 deletions.
82 changes: 67 additions & 15 deletions pkg/root/trusted_root.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ package root
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
"crypto/x509"
"encoding/hex"
"fmt"
Expand Down Expand Up @@ -136,41 +139,66 @@ func ParseTransparencyLogs(tlogs []*prototrustroot.TransparencyLogInstance) (tra
return nil, fmt.Errorf("unsupported hash function for the tlog")
}

tlogEntry := &TransparencyLog{
BaseURL: tlog.GetBaseUrl(),
ID: tlog.GetLogId().GetKeyId(),
HashFunc: hashFunc,
SignatureHashFunc: crypto.SHA256,
}

switch tlog.GetPublicKey().GetKeyDetails() {
case protocommon.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256:
case protocommon.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256,
protocommon.PublicKeyDetails_PKIX_ECDSA_P384_SHA_384,
protocommon.PublicKeyDetails_PKIX_ECDSA_P521_SHA_512:
key, err := x509.ParsePKIXPublicKey(tlog.GetPublicKey().GetRawBytes())
if err != nil {
return nil, err
}
var ecKey *ecdsa.PublicKey
var ok bool
if ecKey, ok = key.(*ecdsa.PublicKey); !ok {
return nil, fmt.Errorf("tlog public key is not ECDSA P256")
return nil, fmt.Errorf("tlog public key is not ECDSA: %s", tlog.GetPublicKey().GetKeyDetails())
}
tlogEntry.PublicKey = ecKey
// This key format has public key in PKIX RSA format and PKCS1#1v1.5 or RSASSA-PSS signature
case protocommon.PublicKeyDetails_PKIX_RSA_PKCS1V15_2048_SHA256,
protocommon.PublicKeyDetails_PKIX_RSA_PKCS1V15_3072_SHA256,
protocommon.PublicKeyDetails_PKIX_RSA_PKCS1V15_4096_SHA256:
key, err := x509.ParsePKIXPublicKey(tlog.GetPublicKey().GetRawBytes())
if err != nil {
return nil, err
}
var rsaKey *rsa.PublicKey
var ok bool
if rsaKey, ok = key.(*rsa.PublicKey); !ok {
return nil, fmt.Errorf("tlog public key is not RSA: %s", tlog.GetPublicKey().GetKeyDetails())
}
tlogEntry.PublicKey = rsaKey
case protocommon.PublicKeyDetails_PKIX_ED25519: //nolint:staticcheck
key, err := x509.ParsePKIXPublicKey(tlog.GetPublicKey().GetRawBytes())
if err != nil {
return nil, err
}
transparencyLogs[encodedKeyID] = &TransparencyLog{
BaseURL: tlog.GetBaseUrl(),
ID: tlog.GetLogId().GetKeyId(),
HashFunc: hashFunc,
PublicKey: ecKey,
SignatureHashFunc: crypto.SHA256,
var edKey ed25519.PublicKey
var ok bool
if edKey, ok = key.(ed25519.PublicKey); !ok {
return nil, fmt.Errorf("tlog public key is not RSA: %s", tlog.GetPublicKey().GetKeyDetails())
}
tlogEntry.PublicKey = edKey
// This key format is deprecated, but currently in use for Sigstore staging instance
case protocommon.PublicKeyDetails_PKCS1_RSA_PKCS1V5: //nolint:staticcheck
key, err := x509.ParsePKCS1PublicKey(tlog.GetPublicKey().GetRawBytes())
if err != nil {
return nil, err
}
transparencyLogs[encodedKeyID] = &TransparencyLog{
BaseURL: tlog.GetBaseUrl(),
ID: tlog.GetLogId().GetKeyId(),
HashFunc: hashFunc,
PublicKey: key,
SignatureHashFunc: crypto.SHA256,
}
tlogEntry.PublicKey = key
default:
return nil, fmt.Errorf("unsupported tlog public key type: %s", tlog.GetPublicKey().GetKeyDetails())
}

tlogEntry.SignatureHashFunc = getSignatureHashAlgo(tlogEntry.PublicKey)
transparencyLogs[encodedKeyID] = tlogEntry

if validFor := tlog.GetPublicKey().GetValidFor(); validFor != nil {
if validFor.GetStart() != nil {
transparencyLogs[encodedKeyID].ValidityPeriodStart = validFor.GetStart().AsTime()
Expand Down Expand Up @@ -297,6 +325,30 @@ func GetTrustedRoot(c *tuf.Client) (*TrustedRoot, error) {
return NewTrustedRootFromJSON(jsonBytes)
}

func getSignatureHashAlgo(pubKey crypto.PublicKey) crypto.Hash {
var h crypto.Hash
switch pk := pubKey.(type) {
case *rsa.PublicKey:
h = crypto.SHA256
case *ecdsa.PublicKey:
switch pk.Curve {
case elliptic.P256():
h = crypto.SHA256
case elliptic.P384():
h = crypto.SHA384
case elliptic.P521():
h = crypto.SHA512
default:
h = crypto.SHA256
}
case ed25519.PublicKey:
h = crypto.SHA512
default:
h = crypto.SHA256
}
return h
}

// LiveTrustedRoot is a wrapper around TrustedRoot that periodically
// refreshes the trusted root from TUF. This is needed for long-running
// processes to ensure that the trusted root does not expire.
Expand Down
94 changes: 93 additions & 1 deletion pkg/root/trusted_root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,34 @@ package root
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"encoding/pem"
"os"
"testing"
"time"

protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/stretchr/testify/assert"
)

const pkixRsa = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3wqI/TysUiKTgY1bz+wd
JfEOil4MEsRASKGzJddZ6x9hb+rn2UVoJmuxN62XI0TMoMn4mukgfCgY6jgTB58V
+/LaeSA8Wz1p4gOxhk1mcgbF4HyxR+xlRgYfH4iSbXy+Ez/8ZjM2OO68fKr4JZEA
5LXZkhJr32JqH+UiFw/wgSPWA8aV0AfRAXHdekJ48B1ChxJTrOJWSPTnj/E0lfLV
srJKtXDuC8T0vFmVU726tI6fODsEE6VrSahvw1ENUHzI34sbfrmrggwPO4iMAQvq
wu2gn2lx6ajWsh806FItiXN+DuizMnx4KMBI0IJynoQpWOFbstGiV0LygZkQ6soz
vwIDAQAB
-----END PUBLIC KEY-----`

const pkixEd25519 = `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA9wy4umF4RHQ8UQXo8fzEQNBWE4GsBMkCzQPAfHvkf/s=
-----END PUBLIC KEY-----`

func TestGetSigstoreTrustedRoot(t *testing.T) {
trustedrootJSON, err := os.ReadFile("../../examples/trusted-root-public-good.json")
assert.Nil(t, err)
Expand All @@ -53,7 +71,7 @@ func (*nonExpiringVerifier) ValidAtTime(_ time.Time) bool {
return true
}

func TestTrustedMaterialCollection(t *testing.T) {
func TestTrustedMaterialCollectionECDSA(t *testing.T) {
trustedrootJSON, err := os.ReadFile("../../examples/trusted-root-public-good.json")
assert.NoError(t, err)

Expand All @@ -73,3 +91,77 @@ func TestTrustedMaterialCollection(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, verifier, verifier2)
}

func TestTrustedMaterialCollectionED25519(t *testing.T) {
trustedrootJSON, err := os.ReadFile("../../examples/trusted-root-public-good.json")
assert.NoError(t, err)

trustedRootProto, err := NewTrustedRootProtobuf(trustedrootJSON)
assert.NoError(t, err)
for _, ctlog := range trustedRootProto.Ctlogs {
ctlog.PublicKey.KeyDetails = protocommon.PublicKeyDetails_PKIX_ED25519
derBytes, _ := pem.Decode([]byte(pkixEd25519))
ctlog.PublicKey.RawBytes = derBytes.Bytes
}

for _, tlog := range trustedRootProto.Tlogs {
tlog.PublicKey.KeyDetails = protocommon.PublicKeyDetails_PKIX_ED25519
derBytes, _ := pem.Decode([]byte(pkixEd25519))
tlog.PublicKey.RawBytes = derBytes.Bytes
}

trustedRoot, err := NewTrustedRootFromProtobuf(trustedRootProto)
assert.NoError(t, err)

for _, tlog := range trustedRoot.rekorLogs {
assert.Equal(t, tlog.SignatureHashFunc, crypto.SHA512)
}

key, _, err := ed25519.GenerateKey(rand.Reader)
assert.NoError(t, err)

ecVerifier, err := signature.LoadED25519Verifier(key)
assert.NoError(t, err)

verifier := &nonExpiringVerifier{ecVerifier}
trustedMaterialCollection := TrustedMaterialCollection{trustedRoot, &singleKeyVerifier{verifier: verifier}}

verifier2, err := trustedMaterialCollection.PublicKeyVerifier("foo")
assert.NoError(t, err)
assert.Equal(t, verifier, verifier2)
}

func TestTrustedMaterialCollectionRSA(t *testing.T) {
trustedrootJSON, err := os.ReadFile("../../examples/trusted-root-public-good.json")
assert.NoError(t, err)

trustedRootProto, err := NewTrustedRootProtobuf(trustedrootJSON)
assert.NoError(t, err)
for _, ctlog := range trustedRootProto.Ctlogs {
ctlog.PublicKey.KeyDetails = protocommon.PublicKeyDetails_PKIX_RSA_PKCS1V15_2048_SHA256
derBytes, _ := pem.Decode([]byte(pkixRsa))
ctlog.PublicKey.RawBytes = derBytes.Bytes
}

for _, tlog := range trustedRootProto.Tlogs {
tlog.PublicKey.KeyDetails = protocommon.PublicKeyDetails_PKIX_RSA_PKCS1V15_2048_SHA256
derBytes, _ := pem.Decode([]byte(pkixRsa))
tlog.PublicKey.RawBytes = derBytes.Bytes
}

trustedRoot, err := NewTrustedRootFromProtobuf(trustedRootProto)
assert.NoError(t, err)

key, err := rsa.GenerateKey(rand.Reader, 2048)
assert.NoError(t, err)

ecVerifier, err := signature.LoadRSAPKCS1v15Verifier(key.Public().(*rsa.PublicKey), crypto.SHA256)
assert.NoError(t, err)

verifier := &nonExpiringVerifier{ecVerifier}
trustedMaterialCollection := TrustedMaterialCollection{trustedRoot, &singleKeyVerifier{verifier: verifier}}

verifier2, err := trustedMaterialCollection.PublicKeyVerifier("foo")
assert.NoError(t, err)
assert.Equal(t, verifier, verifier2)
}

0 comments on commit 40042e6

Please sign in to comment.