Skip to content

x/crypto/ssh: CertChecker.CheckCert ability to check against multiple principals #38829

Open
@candlerb

Description

@candlerb

What version of Go are you using (go version)?

$ go version
go version go1.13.4 linux/amd64

Does this issue reproduce with the latest release?

N/A (x/crypto is separate dependency)

What operating system and processor architecture are you using (go env)?

N/A for this issue (Linux x86_64)

What did you do?

I was testing pam-ussh, which uses x/crypto to validate SSH certificates via ssh-agent before deciding to authorize sudo.

The issue as it relates to pam-ussh is described here: uber/pam-ussh#15

pam-ussh has its own logic for checking principals in certificates. However the certificate was being rejected by CheckCert() before it even got to that point.

What's happening is this: pam-ussh calls CheckCert() to see if the certificate is valid. However it has a fixed policy, which is: if the certificate has any principals, then the supplied "principal" string value must appear in that list. Code: https://github.com/golang/crypto/blob/master/ssh/certs.go#L384

In the case of pam-ussh: because you must pass in a principal, it passes in the current username, and this enforces a policy that the bare username must appear as a principal in the certificate.

This is equivalent to the default login policy with OpenSSH if you don't provide an AuthorizedPrincipalsFile or AuthorizedPrincipalsCommand option. However if you do, the given file or script defines a list of possible principals which are permitted to appear in the certificate.

CheckCert() doesn't appear to be able to handle this case: if the certificate has any principals, then only one item can be provided to match against it. If you pass an empty string then it will require the certificate to contain the empty principal (it's unclear if that's even a thing).

What did you expect to see?

The solution I prefer is to have a variant of CheckCert() which takes a slice of principals, instead of a single principal. If the certificate contains any principals, then at least one of them must be in this list. This replicates the checking policy of OpenSSH.

(Aside: this implies that an empty list will never authorize a certificate which contains principals; that is I believe consistent with OpenSSH)

The existing CheckCert() can then become a wrapper around this new function, passing its "principal" argument as a 1-element slice.

Other options I considered:

  • Pass a callback function to CheckCert for checking principals

  • Add another callback function to the CertChecker struct. It would still receive the "principal" argument from CheckCert, but your callback function could treat it differently, e.g. parse it as a comma-separated list. If the function is nil, it would use the current logic.

  • Have a way to disable principal checking altogether in CheckCert, so that the caller can do their own check. Passing empty string for principal might be a way to do that, which would avoid any change to call signatures, although it depends whether certificates with empty principals are a useful "thing" or not. If not, it would simply be:

            if len(principal) > 0 and len(cert.ValidPrincipals) > 0 {
    

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions