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

feat: add support for additional transparency log key types #197

Merged
merged 5 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
60 changes: 45 additions & 15 deletions pkg/root/trusted_root.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ package root
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"encoding/hex"
"fmt"
Expand Down Expand Up @@ -136,41 +138,69 @@ 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,
Copy link
Contributor

Choose a reason for hiding this comment

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

The signature hash algorithm will differ depending on the signature scheme. I'd recommend a function like https://github.com/slsa-framework/slsa-verifier/blob/d96b9777090694fa5096ee1b9c710a46b5a66f5e/cli/slsa-verifier/verify/verify_vsa.go#L95-L117 to handle the mapping

tldr - RSA can always be SHA256, ECDSA P256 and P384 should be SHA256, P521 SHA512, and ed25519 is SHA512.

}

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,
protocommon.PublicKeyDetails_PKIX_ECDSA_P256_HMAC_SHA_256: //nolint:staticcheck
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't need to support PublicKeyDetails_PKIX_ECDSA_P256_HMAC_SHA_256, we've marked this as deprecated.

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,
protocommon.PublicKeyDetails_PKIX_RSA_PSS_2048_SHA256,
Copy link
Contributor

Choose a reason for hiding this comment

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

We can skip support for PSS, that would require also changing how we call LoadVerifier, which would require conditionally adding an option to use PSS over PKCS1v1.5. Given loading this verifier is in another package, it'd be a bit too much refactoring to try to connect these for not much gain.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have removed PKIX-PSS but I havent removed PKIX-PKCS1v1.5

protocommon.PublicKeyDetails_PKIX_RSA_PSS_3072_SHA256,
protocommon.PublicKeyDetails_PKIX_RSA_PSS_4096_SHA256:
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 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, protocommon.PublicKeyDetails_PKIX_ED25519_PH: //nolint:staticcheck
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, we can skip PKIX_ED25519_PH, the prehash variant of ed25519 isn't needed, and it would require making changes to LoadVerifier.

key, err := x509.ParsePKIXPublicKey(tlog.GetPublicKey().GetRawBytes())
if err != nil {
return nil, err
}
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())
}

transparencyLogs[encodedKeyID] = tlogEntry

if validFor := tlog.GetPublicKey().GetValidFor(); validFor != nil {
if validFor.GetStart() != nil {
transparencyLogs[encodedKeyID].ValidityPeriodStart = validFor.GetStart().AsTime()
Expand Down
90 changes: 89 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 pkixRsaPssSHA256 = `-----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,73 @@ 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)

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_PSS_2048_SHA256
derBytes, _ := pem.Decode([]byte(pkixRsaPssSHA256))
ctlog.PublicKey.RawBytes = derBytes.Bytes
}

for _, tlog := range trustedRootProto.Tlogs {
tlog.PublicKey.KeyDetails = protocommon.PublicKeyDetails_PKIX_RSA_PSS_2048_SHA256
derBytes, _ := pem.Decode([]byte(pkixRsaPssSHA256))
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.LoadRSAPSSVerifier(key.Public().(*rsa.PublicKey), crypto.SHA256, nil)
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)
}
Loading