Skip to content

Create Algorithm Registry API#1601

Merged
Hayden-IO merged 10 commits intosigstore:mainfrom
trail-of-forks:alex/algorithm-registry-api
Feb 4, 2025
Merged

Create Algorithm Registry API#1601
Hayden-IO merged 10 commits intosigstore:mainfrom
trail-of-forks:alex/algorithm-registry-api

Conversation

@tetsuo-cpp
Copy link
Copy Markdown
Contributor

Summary

As part of our work to support configurable crypto algorithms across the Sigstore stack, we've added a KnownSignatureAlgorithm enumeration to protobuf-specs to reflect what signature algorithms are part of the official Sigstore algorithm registry. This PR adds some helpers and APIs to make use of this registry in Sigstore services.

Release Note

  • Added GetAlgorithmDetails helper to access the key algorithm, hash algorithm and other algorithm-specific parameters for a given signature algorithm.
  • Added AlgorithmRegistryConfig API to check a public key and hash algorithm combination against a set of accepted signature algorithms.
  • Added FormatSignatureAlgorithmFlag and ParseSignatureAlgorithmFlag to convert the signature algorithm type to and from a string representation that can be used as a CLI parameter.

Documentation

@tetsuo-cpp
Copy link
Copy Markdown
Contributor Author

I'm working on some unit tests now.

@tetsuo-cpp tetsuo-cpp changed the title Create Algorithm Registry Config API Create Algorithm Registry API Jan 18, 2024
return a.hashType == hashType
}

var algorithmDetails = []algorithmDetailsImpl{
Copy link
Copy Markdown
Contributor Author

@tetsuo-cpp tetsuo-cpp Jan 18, 2024

Choose a reason for hiding this comment

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

I moved away from that design where I had an interface and an implementation for each signature algorithm as it was really doing anything for us. Each AlgorithmRegistryConfig now holds pointers to algorithms in this list.

return fmt.Errorf("signing algorithm not permitted: %T, %s", key, hash)
}

// FormatSignatureAlgorithmFlag formats a v1.KnownSignatureAlgorithm to a string that conforms to the conventions of CLI
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This CLI stuff isn't terribly efficient but I don't expect it to be on the hot path since this is just initialisation code.

@tetsuo-cpp
Copy link
Copy Markdown
Contributor Author

CC: @ret2libc @woodruffw @haydentherapper

@woodruffw woodruffw force-pushed the alex/algorithm-registry-api branch from 9eec947 to 1be7204 Compare January 18, 2024 17:54
@ret2libc
Copy link
Copy Markdown
Contributor

@tetsuo-cpp you can see https://github.com/trail-of-forks/cosign/compare/support-ed25519ph...trail-of-forks:cosign:signing-algorithm-flag?expand=1 on how I might be using the registry in cosign. Hope it helps us define a good API!

@woodruffw woodruffw force-pushed the alex/algorithm-registry-api branch 2 times, most recently from 5b727de to dfb835b Compare January 19, 2024 20:11
@woodruffw
Copy link
Copy Markdown
Member

(Force-pushed to fix the signoffs.)

}

// IsAlgorithmPermitted checks whether a given public key/hash algorithm combination is permitted by a registry config.
func (registryConfig AlgorithmRegistryConfig) IsAlgorithmPermitted(key crypto.PublicKey, hash crypto.Hash) bool {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How do you foresee this being used with KMS keys? Should the caller be required to inspect the KMS key for its key and hash (we have key, we're missing hash, but it's being implemented in #1426). Should each KMS provider has its own registry?(as a service, this would be a bit annoying to use, since you'd have to consult a registry of regstries)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think it makes most sense for the calling code to inspect the KMS key and hash, and check against the registry to see whether it should use that key or not.

I don't think it makes sense for each KMS provider to own its own registry, but perhaps the kms.SignerVerifier API could take an optional AlgorithmRegistryConfig and do the checking there? But I'd lean towards not tangling the APIs together like that and just let the calling code compose these calls as required.

What are your thoughts?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That sounds good to me. Long-term I'd lean towards the proposal to take in a config, just to avoid each client having to reimplement this, but I don't think it's something that's blocking regardless, just thinking through how we'll handle users who provide their own KMS keys.

@tetsuo-cpp tetsuo-cpp requested a review from Hayden-IO January 23, 2024 13:30
Hayden-IO
Hayden-IO previously approved these changes Jan 24, 2024
}

// IsAlgorithmPermitted checks whether a given public key/hash algorithm combination is permitted by a registry config.
func (registryConfig AlgorithmRegistryConfig) IsAlgorithmPermitted(key crypto.PublicKey, hash crypto.Hash) bool {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That sounds good to me. Long-term I'd lean towards the proposal to take in a config, just to avoid each client having to reimplement this, but I don't think it's something that's blocking regardless, just thinking through how we'll handle users who provide their own KMS keys.

Hayden-IO
Hayden-IO previously approved these changes Jan 24, 2024
// GetAlgorithmDetails retrieves a set of details for a given v1.KnownSignatureAlgorithm flag that allows users to
// introspect the public key algorithm, hash algorithm and more.
func GetAlgorithmDetails(knownSignatureAlgorithm v1.KnownSignatureAlgorithm) (AlgorithmDetails, error) {
for _, detail := range algorithmDetails {
Copy link
Copy Markdown
Member

@bobcallaway bobcallaway Jan 24, 2024

Choose a reason for hiding this comment

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

maybe turn this into a map[v1.KnownSignatureAlgorithm]AlgorithmDetails ?

Copy link
Copy Markdown
Contributor Author

@tetsuo-cpp tetsuo-cpp Jan 25, 2024

Choose a reason for hiding this comment

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

I was thinking about this, but my reasons not to were:

  • If I do that, I have to match the v1.KnownSignatureAlgorithm in the AlgorithmDetails with the one in the map key, which is another opportunity for subtle typos that can introduce bugs. I can't remove it from AlgorithmDetails since we need it for things like AlgorithmRegistryConfig.ListPermittedAlgorithms.
  • The algorithmDetails array only includes an entry for each signature algorithm in the registry which by design, is a small, constrained set of algorithms. I haven't benchmarked and to be honest, I don't know much about Go's performance characteristics, but I wouldn't expect the map to be faster than a simple linear search for this number of elements.
  • This array isn't part of the public API so if my assumptions are wrong in the long term, we can easily walk this decision back if we need to without introducing breaking changes.

Happy to change it over to a map, if you'd prefer though.

Copy link
Copy Markdown
Contributor

@Hayden-IO Hayden-IO left a comment

Choose a reason for hiding this comment

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

Overall LGTM, please take a look at the lint failure

if err != nil {
t.Errorf("unexpected error creating algorithm registry config: %v", err)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we also add a test for RSA keys being permitted?

It might be overkill, but ideally we'd have a test for every supported key type, to have maximum code coverage.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@tetsuo-cpp tetsuo-cpp requested a review from Hayden-IO January 29, 2024 22:43
@Hayden-IO
Copy link
Copy Markdown
Contributor

@kommendorkapten Do you want to take a look at this PR? There's an open question of how this should interact with PublicKeyDetails (thread)

@kommendorkapten
Copy link
Copy Markdown
Member

This is great 🚀
This is quite related to the existing PublicKeyDetails, which today is used when distributing the trust root, to indicate how to interpret and use public key material, e.g. the public key for Rekor.

I believe there is an overlap between KnownSignatureAlgorithm and PublicKeyDetails, and I fear this may become a bit complicated for clients to work with. How do we feel about merging them into PublicKeyDetails?

PublicKeyDetails captures the signature algorithm used (albeit a bit underspecified for RSA keys, but that can easily be fixed), which is exactly what KnownSignatureAlgorithm does.

The only differences I see is the naming, and that PublicKeyDetails also indicates the encoding of the public key material, but I would think that it wouldn't really matter, I would say that it's only RSA keys that in practice may have different encodings (PKCS vs PKIX).

One can argue that the two types might solve slightly different problems, but as the overlap is so big, I would feel better if they could be combined, otherwise the clients would need to translate between the two formats, which I feel is sub-optimal.

Thoughts?

@tetsuo-cpp
Copy link
Copy Markdown
Contributor Author

The only differences I see is the naming, and that PublicKeyDetails also indicates the encoding of the public key material, but I would think that it wouldn't really matter, I would say that it's only RSA keys that in practice may have different encodings (PKCS vs PKIX).

Aren't the RSA values also missing the hash algorithm, or is that implied somehow? That was the main reason I didn't add to PublicKeyDetails (as well as the key encoding). It seemed to me that the KnownSignatureAlgorithm enum would be capturing a slightly different set of information than what's in PublicKeyDetails.

If it's fine to overwrite the values in PublicKeyDetails with what's in KnownSignatureAlgorithm (that is, if we can avoid distinguishing how the key is encoded as well as add hash functions and key sizes for each of the RSA options), then I don't see anything stopping us from merging the two.

@kommendorkapten
Copy link
Copy Markdown
Member

Aren't the RSA values also missing the hash algorithm, or is that implied somehow?

I don't remember the reason we left the RSA values underspecified 🤔 , but adding new values with them in there would make no harm.

Yeah, adding the new values missing in PublicKeyDetails is preferable IMHO. The old RSA values can be marked as [deprecated = true]. I believe RSA are the only ones that needs updates, the EC types matches what's in KownSignatureAlgorithms (but only P256 is listed). I would also be tempted to mark deterministic ECDSA as deprecated, I don't think anyone is using that?

@tetsuo-cpp
Copy link
Copy Markdown
Contributor Author

Yeah, adding the new values missing in PublicKeyDetails is preferable IMHO. The old RSA values can be marked as [deprecated = true]. I believe RSA are the only ones that needs updates, the EC types matches what's in KownSignatureAlgorithms (but only P256 is listed). I would also be tempted to mark deterministic ECDSA as deprecated, I don't think anyone is using that?

That seems reasonable.

I had another idea though: what do you think about marking the entire PublicKeyDetails enum as deprecated and then move any code using this enum to use KnownSignatureAlgorithms? That way, we can maintain backwards compatibility as you're suggesting without diverging from the algorithm registry markdown and having lots of deprecated options mixed in with legitimate ones in the short term.

@ret2libc
Copy link
Copy Markdown
Contributor

Overall LGTM. As a meta comment, my only reservation is it's hard to know if we've gotten the API right here without seeing this in use. I'm not really concerned though since I don't expect this API to be used outside of the agility work initially.

@haydentherapper you can see its use in other pending PRs like sigstore/rekor#1974 , sigstore/cosign#3497 , sigstore/fulcio#1517 (this last one might be outdated)

@ret2libc ret2libc force-pushed the alex/algorithm-registry-api branch 2 times, most recently from 4b47bc2 to 51e2bd0 Compare January 21, 2025 09:31
Signed-off-by: Riccardo Schirone <[email protected]>
Signed-off-by: Alex Cameron <[email protected]>
Signed-off-by: William Woodruff <[email protected]>
Co-authored-by: Alex Cameron <[email protected]>
Co-authored-by: Riccardo Schirone <[email protected]>
@ret2libc ret2libc force-pushed the alex/algorithm-registry-api branch from 51e2bd0 to 9b265f4 Compare January 21, 2025 09:33
Copy link
Copy Markdown
Contributor

@ret2libc ret2libc left a comment

Choose a reason for hiding this comment

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

I have squashed the whole history into 1 commit because there were very old iterations that did not make sense anymore.

The PR was tested with fulcio/rekor/cosign.

With regards to the API and its uses: to be honest a few functions are not used anywhere, so we may make the API even smaller and hide more things, if we want. So if/when it grows/changes it will be easier to do so.

In particular, these functions are not used in any of the other components:

  • HasAlgorithmDetails
  • ListPermittedAlgorithms
  • registry.GetAlgorithmDetails
  • GetAlgorithmDetails

@ret2libc
Copy link
Copy Markdown
Contributor

ping @haydentherapper

There has been a lot of back and forth on this PR and the others. I have updated all of them, so I'd appreciate a new review on this.

@Hayden-IO
Copy link
Copy Markdown
Contributor

Thanks @ret2libc for the updates! I did a review over the PR and reviewed my old comments to remember where we had ended - I think the PR is looking good!

For those functions, if they're unused, I'd agree that we can omit then. I'd rather keep the API surface smaller and expand it later as needed. Do you recall why we had proposed those? It seems like those are mostly focused on exposing the configuration mapping, which I assume would only be relevant to developers who could inspect the source code.

@ret2libc
Copy link
Copy Markdown
Contributor

Thanks @ret2libc for the updates! I did a review over the PR and reviewed my old comments to remember where we had ended - I think the PR is looking good!

For those functions, if they're unused, I'd agree that we can omit then. I'd rather keep the API surface smaller and expand it later as needed. Do you recall why we had proposed those? It seems like those are mostly focused on exposing the configuration mapping, which I assume would only be relevant to developers who could inspect the source code.

I think they were just exposed to have a "nice API" and maybe also because we did not know how we would have ended up using the API when we started with this.

I'm going to clean the API!

Hayden-IO
Hayden-IO previously approved these changes Feb 3, 2025
Copy link
Copy Markdown
Contributor

@Hayden-IO Hayden-IO left a comment

Choose a reason for hiding this comment

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

This looks great! Any other changes or is this good to merge?

Signed-off-by: Riccardo Schirone <[email protected]>
bobcallaway
bobcallaway previously approved these changes Feb 4, 2025
@Hayden-IO
Copy link
Copy Markdown
Contributor

Going to merge now, thanks all!

@Hayden-IO Hayden-IO merged commit 63abeeb into sigstore:main Feb 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants