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

Generic signature API #143

Merged
merged 20 commits into from
Aug 19, 2020
Merged

Generic signature API #143

merged 20 commits into from
Aug 19, 2020

Conversation

bwesterb
Copy link
Member

@bwesterb bwesterb commented Jul 15, 2020

Used to easily add support for new (PQ) signature schemes to our fork of Go and cfssl.

@bwesterb
Copy link
Member Author

Another reason is that I'm quickly prototyping integration into Go stdlib to find possible issues with the API. I found one already: we can't use PubicKeyAlgorithm() x509.PublicKeyAlgorithm in our interface for that would cause an import cycle.

@bwesterb bwesterb force-pushed the signapi branch 4 times, most recently from 42fc6ae to 1e2cb7f Compare July 22, 2020 09:54
@bwesterb
Copy link
Member Author

@armfazh @claucece Have another look.

@bwesterb bwesterb force-pushed the signapi branch 9 times, most recently from 29ed92c to 0ca32e0 Compare July 22, 2020 22:14
@claucece
Copy link
Contributor

I'll check this tomorrow ;)

sign/api.go Outdated Show resolved Hide resolved
sign/api.go Outdated Show resolved Hide resolved
sign/api.go Outdated Show resolved Hide resolved
sign/eddilithium3.go Outdated Show resolved Hide resolved
sign/eddilithium3.go Outdated Show resolved Hide resolved
sign/eddilithium3.go Outdated Show resolved Hide resolved
@claucece
Copy link
Contributor

I think it is looking good. Maybe we should be more clear on some names and all, though

sign/api.go Outdated Show resolved Hide resolved
sign/api.go Outdated Show resolved Hide resolved
sign/api.go Outdated
// XXX add a (compile time?) lookup table
name = strings.ToLower(name)
for _, scheme := range schemes {
if strings.ToLower(scheme.Name()) == name {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we don't need to repeat here the 'ToLower'.. but maybe it will be a safe security measure...

Copy link
Member Author

Choose a reason for hiding this comment

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

If we would remove this ToLower() then we will never match any scheme that has an upper case character in the return of its scheme.Name().

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, but perhaps the scheme.Name() should always be enforced to be lower case... what do you think?

Copy link
Member Author

Choose a reason for hiding this comment

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

@armfazh Removed case insensitivity again. Did you do that on purpose @armfazh ?

sign/api.go Outdated Show resolved Hide resolved
sign/api.go Outdated Show resolved Hide resolved
func (s *edDilithium3Scheme) Sign(sk PrivateKey, message []byte,
opts *SignatureOpts) []byte {
sig := make([]byte, eddilithium3.SignatureSize)
if opts != nil && opts.Context != "" {
Copy link
Contributor

Choose a reason for hiding this comment

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

What if only opts.Context != ""?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't understand. For opts.Context to make sense, we must have opts != nil.

Copy link
Contributor

Choose a reason for hiding this comment

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

if any of the options is not used, then is ignored.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't understand — could you rephrase perhaps?

@bwesterb
Copy link
Member Author

Maybe we should be more clear on some names and all, though

Sure, which?

@armfazh
Copy link
Contributor

armfazh commented Jul 25, 2020

I just added a generic interface for four signatures, there are some methods marked with TODO.
Also I moved some methods into the circl/pki package.

@claucece
Copy link
Contributor

Sure, which?

Let me thinking of a nice naming convention and propose something over here tomorrow.

sign/ed25519/signapi.go Outdated Show resolved Hide resolved
sign/api/api.go Outdated Show resolved Hide resolved
@bwesterb
Copy link
Member Author

bwesterb commented Aug 5, 2020

@armfazh

  • I dislike that SchemeByName is now under the github.com/cloudflare/circl/sign/api package.

I don't see a way to solve this without making something else ugly.

  • Providing a context to a scheme that does not support it is completely ignored. It should be an error.

I've changed that behaviour: provided a context where none is supported causes an error.

@bwesterb
Copy link
Member Author

bwesterb commented Aug 5, 2020

I've also removed the Hash from the SignatureOpts as it's not used — we can add it back in if we have a scheme that actually uses it.

@bwesterb
Copy link
Member Author

bwesterb commented Aug 5, 2020

@armfazh @claucece I've addressed all comments. Before starting with final polish (i.e. better documentation & examples), I think we still need to discuss some API issues:

  • Are we really happy with having sign package which defines Scheme and sign/api package which actually lists them? Isn't schemes perhaps a better name than api as the sign package itself defines the API. Then schemes.All() and scheme.ByName() would be nice to write.
  • Do we want Scheme names to be case insensitive? This was the case but @armfazh reversed it. This is required for easy integration with cfssl.
  • Doesn't ed25519 have a variant that support contexts? Should we add this to the Ed25519 scheme or have a separate Ed25519Ctx scheme? I guess it's best to have separate schemes for these variants — @claucece what do you think?

@armfazh
Copy link
Contributor

armfazh commented Aug 5, 2020

  • Are we really happy with having sign package which defines Scheme and sign/api package which actually lists them? Isn't schemes perhaps a better name than api as the sign package itself defines the API. Then schemes.All() and scheme.ByName() would be nice to write.

Agree on s/api/schemes/

  • Do we want Scheme names to be case insensitive? This was the case but @armfazh reversed it. This is required for easy integration with cfssl.

I don't have a strong opinion on this. Feel free to bring back that code.

  • Doesn't ed25519 have a variant that support contexts? Should we add this to the Ed25519 scheme or have a separate Ed25519Ctx scheme? I guess it's best to have separate schemes for these variants — @claucece what do you think?

That variant would be another named scheme Ed25519Ctx, which is separated from the current Ed25519 scheme.

I've also removed the Hash from the SignatureOpts as it's not used — we can add it back in if we have a scheme that actually uses it.

I added that the Hash field to make the structure compatible with the crypto.SignerOpts. So, we can receive a crypto.SignerOpts, and internally convert it to a different type.

I dislike that SchemeByName is now under the github.com/cloudflare/circl/sign/api package.

I also didn't like it at all. But, that's the way I found for avoiding circle dependencies. Renaming sign/api to sign/schemes can alleviate a bit its usage.

"github.com/cloudflare/circl/sign"
)

var Scheme sign.Scheme = &scheme{}
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand why to take the address of a zero-sized value. You can implement the interface with scheme and not with *scheme.

Copy link
Member Author

Choose a reason for hiding this comment

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

Under the hood an instance of an interface is always a pair of a pointer and a concrete type, so instantiating it with a pointer is closer to what really happens. The assembly generated btw is exactly the same.

Copy link
Contributor

Choose a reason for hiding this comment

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

The difference is on the assembly calls.
call AX vs call "".(*Impl2).A(SB)
one call is with a register and the other is with a pointer.

I am not convinced why to use pointer when they are not needed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oops, I simplified my example too much.

Take a look at this one

Creating an instance of an interface with either a empty struct or a pointer to an empty struct results in the pointer runtime.zerobase. (The code of alloc1 and alloc2 is the same. This time I checked properly.)

The generic way to run a function on an interface involves a virtual call (call ax). So interestingly the Go compiler optimised the call with the pointer receiver, but not the call with the value receiver. I don't see a reason why it couldn't optimise the one with the value receiver as well and perhaps it will in the future.

So using pointers or not does not actually change anything when using empty structs (and the calling code doesn't know the types.) And if it does it optimizes the pointer case better. That's not the reason I use pointers though — as I said I prefer putting them here as it's closer to what Go actually does under the hood: an instance of an interface is always a pointer.

Copy link
Contributor

Choose a reason for hiding this comment

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

So interestingly the Go compiler optimised the call with the pointer receiver, but not the call with the value receiver.

I want to learn more about this.

So using pointers or not does not actually change anything when using empty structs.

Agree.

sign/sign.go Show resolved Hide resolved
@bwesterb
Copy link
Member Author

bwesterb commented Aug 6, 2020

Agree on s/api/schemes/

Done. Updated this branch, Go fork and cfssl fork.

I added that the Hash field to make the structure compatible with the crypto.SignerOpts. So, we can receive a crypto.SignerOpts, and internally convert it to a different type.

The type of Scheme.Sign is Sign(sk PrivateKey, message []byte, opts *SignatureOpts) []byte so that wouldn't accept a crypto.SignerOpts anyway now. (Note that there are two sign functions: one on the scheme and one on the PrivateKey — the one on the private key does accept a crypto.SignerOpts but does not allow for the advanced options like Context.)

@armfazh
Copy link
Contributor

armfazh commented Aug 6, 2020

the one on the private key does accept a crypto.SignerOpts but does not allow for the advanced options like Context.)

I had the same intuition, but here is the trick. Even if we announce to receive a crypto.SignerOpts, we could pass a Sign.SignatureOpts (which must have field Hash) and internally convert the parameter into a Sign.SignatureOpts struct. (I learn this trick from @claucece)

@claucece
Copy link
Contributor

Are we really happy with having sign package which defines Scheme and sign/api package which actually lists them? Isn't schemes perhaps a better name than api as the sign package itself defines the API. Then schemes.All() and scheme.ByName() would be nice to write.

Yes!

Do we want Scheme names to be case insensitive? This was the case but @armfazh reversed it. This is required for easy integration with cfssl.

Mmm... It will be better case insensitive.

Doesn't ed25519 have a variant that support contexts? Should we add this to the Ed25519 scheme or have a separate Ed25519Ctx scheme? I guess it's best to have separate schemes for these variants — @claucece what do you think?

There are two: the ctx and the ph. The schemes should be there on the ed25519 API.

@claucece
Copy link
Contributor

I had the same intuition, but here is the trick. Even if we announce to receive a crypto.SignerOpts, we could pass a Sign.SignatureOpts (which must have field Hash) and internally convert the parameter into a Sign.SignatureOpts struct. (I learn this trick from @claucece)

Exactly! Then, the "Golang-way" API does not change ;)

@claucece
Copy link
Contributor

@bwesterb check what I did with the ed448 and ed25519 API, specially the usage of this: https://github.com/cloudflare/circl/blob/master/sign/ed25519/ed25519.go#L69

@bwesterb bwesterb changed the title [WIP] signature API Generic signature API Aug 12, 2020
@bwesterb
Copy link
Member Author

I had the same intuition, but here is the trick. Even if we announce to receive a crypto.SignerOpts, we could pass a Sign.SignatureOpts (which must have field Hash) and internally convert the parameter into a Sign.SignatureOpts struct. (I learn this trick from @claucece)

Yes, that's for a method on the private key, but here we're dealing with a top level function.

(For the Sign method on the private key it's actually a bit tricky. If we use the same type for an ed25519 and ed25519ctx private key, then we can't know whether it was used for the scheme ed25519 or ed25519ctx. I'd like to propose to fix this in a coming pull request where we add schemes for the variants of ed*.)

@bwesterb
Copy link
Member Author

@armfazh @claucece I'm happy with the PR as it is now (assuming we'll be able to make changes to the API before we tag it.) Do you agree?

@claucece
Copy link
Contributor

(For the Sign method on the private key it's actually a bit tricky. If we use the same type for an ed25519 and ed25519ctx private key, then we can't know whether it was used for the scheme ed25519 or ed25519ctx. I'd like to propose to fix this in a coming pull request where we add schemes for the variants of ed*.)

Mmm.. ok.

Do you agree?

Sure!

Copy link
Contributor

@armfazh armfazh left a comment

Choose a reason for hiding this comment

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

It seems ready to go.

pki/pki.go Outdated Show resolved Hide resolved
pki/pki.go Show resolved Hide resolved
pki/pki.go Outdated Show resolved Hide resolved
@bwesterb bwesterb merged commit 0c3b4ec into master Aug 19, 2020
@bwesterb bwesterb deleted the signapi branch August 19, 2020 09:27
@bwesterb
Copy link
Member Author

Thank you, I merged it. I created an issue ( #147 ) to track desired improvements to this API.

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.

4 participants