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

smartcard handler for request authentication and CSR generation #34

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

py4chen
Copy link
Contributor

@py4chen py4chen commented Jan 3, 2023

PR defines a smartcard handler to authenticate and generate CSRs for yubikey-backed SSH certificates. PR also includes 3 authentication modules and 1 csr module, which can be plugged into the smartcard handler by setting the ysshra configuration.

A sample smartcard handler config will be like:

"handlers":
    {
        "smartcard":
        {
            "authn": [
                {
                    "module": "f9_verify",
                    "f9_certs_dir": "/opt/ysshra/f9_certs/"
                },
                {
                    "module": "slot_attest",
                    "slot": "9a",
                    "piv_root_ca": "/opt/ysshra/piv_root_ca.pem",
                    "u2f_root_ca": "/opt/ysshra/u2f_root_ca.pem",
                },
                {
                    "module": "slot_attest",
                    "slot": "9e",
                    "piv_root_ca": "/opt/ysshra/piv_root_ca.pem",
                    "u2f_root_ca": "/opt/ysshra/u2f_root_ca.pem",
                },
                {
                    "module": "slot_serial",
                    "slot": "9e",
                    "yubikey_mappings": "/opt/ysshra/yubikey_mappings",
                },
            ],
            "csr":  [
                {
                    "module": "smartcard_hardkey",
                    "_comment": "touchSSH",
                    "is_firefighter": false,
                    "touch_policy": 3,
                    "principals": "<logname>,<logname>:touch",
                    "slot": "9e",
                    "cert_validity_sec": 86400
                }
            ],
            "key_identifiers": {
                "default": "ssh-user-key",
            }
        }
    },

image

Notes:

  • There are no unit tests for module slot_attest, which will be covered in another PR.

I confirm that this contribution is made under the terms of the license found in the root directory of this repository's source tree and that I have the authority necessary to make this contribution on behalf of its copyright owner.

@py4chen py4chen marked this pull request as ready for review January 5, 2023 10:59
@py4chen py4chen changed the title [DNR] smartcard authn & csr modules smartcard handler for request authentication and CSR generation Jan 5, 2023
agent/yubiagent/generate_test.go Outdated Show resolved Hide resolved
agent/yubiagent/slot.go Outdated Show resolved Hide resolved
agent/yubiagent/slot.go Outdated Show resolved Hide resolved
}

// RegisterCSR registers the given certificate signing request for the key slot.
func (s *SlotAgent) RegisterCSR(csr *proto.SSHCertificateSigningRequest) {

Choose a reason for hiding this comment

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

I'm not quite sure why we need to keep the CSR. Can you provide use cases?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need both CSR and AddCertsToAgent in struct slot, because the struct implements the interface csr.AgentKey. In this way, gensign main logic could extract all the CSRs for a key pair (or a key slot), sign them, and add the signed certificates to the corresponding agent.

	for _, agentKey := range csrAgentKeys {
		var (
			certs    []ssh.PublicKey
			comments []string
		)
		for _, csr := range agentKey.CSRs() {
			cert, comment, err := signer.Sign(ctx, csr)
			if err != nil {
				return fmt.Errorf("failed to sign CSR: %v", err)
			}
			certs = append(certs, cert...)
			comments = append(comments, comment...)
		}
		err = agentKey.AddCertsToAgent(certs, comments)
		if err != nil {
			return fmt.Errorf("failed to add certificates into the agent: %v", err)
		}
	}

Copy link
Contributor Author

@py4chen py4chen Jan 9, 2023

Choose a reason for hiding this comment

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

image

Choose a reason for hiding this comment

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

To me, it feels like we probably need to review the design. Now, it is more like we need those methods exported because the implementation requires this to do so, not the methods is available to the struct or object.
For more specific, an Yubikey PIV slot can contain private key, and certificate but not CSRs. It doesn't make sense the retrieve it from the agent just because the implementation needs it. Similar, the ssh-agent doesn't hold the CSRs.
Also, allow a slot to retrieve its agent might be helpful in implementation, but doesn't make sense to expose it.

Choose a reason for hiding this comment

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

Checking the Handler interface
It probably will make more sense to just pass the csr.Signer into the handler to sign the certificate and store into the agent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Per discussion, the new interface will be like:

type Handler interface {
	Name() string
	Authenticate(params *csr.ReqParam) error
	Sign(signer, param) error // 1. Gen CSR, 2. Sign, 3. Add into agent
}

}

// Agent returns the yubiagent.
func (s *SlotAgent) Agent() YubiAgent {

Choose a reason for hiding this comment

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

Why we want to export this? Can you provide use cases?

Copy link
Contributor Author

@py4chen py4chen Jan 9, 2023

Choose a reason for hiding this comment

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

There are 2 use cases:

  1. During ssh key challenge, we will need the agent of that key slot to invoke "Sign()"
    https://github.com/theparanoids/ysshra/blob/main/agent/ssh/agent.go#L74-L85
  2. During attestation, I want to use the same agent of that key slot to fetch the f9 certificate.
f9Cert, err := a.slot.Agent().ReadSlot("f9")

For (2), it could be eliminated by creating a slot object for f9. For (1), I don't like to expose another function Sign() for struct Slot. Thus, I expose Agent()

Copy link

@adavis10006 adavis10006 Jan 10, 2023

Choose a reason for hiding this comment

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

This doesn't sound right. It make more sense to implement Sign() for a Slot and for the f9, it should be hold inside the struct

type authn struct {
	attest       *yubiagnet.Slot
	slotAgent *yubiagent.Slot
	conf      *conf
}

agent/yubiagent/slot.go Outdated Show resolved Hide resolved
config/module.go Outdated Show resolved Hide resolved
modules/authn_slot_attest/conf.go Outdated Show resolved Hide resolved
modules/authn_slot_serial/conf.go Outdated Show resolved Hide resolved
modules/csr_smartcard_hardkey/conf.go Outdated Show resolved Hide resolved
// but now it needs 8 decimal digits.
// For those old yubikeys with serial numbers of 7 decimal digits,
// the file name of the corresponding f9 cert is prepended with a `0`.
if len(f9SerialNumStr) < 8 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

can we prepend 8 - len(f9SerialNumStr) 0s instead of assuming the old ones to be 7 digit long and only prepending one 0?

default:
return false
}
// ed25519 is non-supported key algo for now.
Copy link
Collaborator

Choose a reason for hiding this comment

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

ed25519 is supported by yubikey but ysshra doesn't yet support it, correct? If so, can we add a todo here?

Choose a reason for hiding this comment

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

According to https://support.yubico.com/hc/en-us/articles/360013647820-YubiKey-4C, it is not supported in PIV mode.

switch pk.Size() * 8 {
case 2048:
return true
default:
Copy link
Collaborator

Choose a reason for hiding this comment

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

any reason RSA 4096 and 8192 is not supported?

Choose a reason for hiding this comment

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

According to https://support.yubico.com/hc/en-us/articles/360013647820-YubiKey-4C, it is not supported in PIV mode.

// NOTE: The following id is the touch policy stored in attestation certificate.
// Refer: https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
if ext.Id.String() == "1.3.6.1.4.1.41482.3.8" {
touch = keyid.TouchPolicy(ext.Value[1])
Copy link
Collaborator

Choose a reason for hiding this comment

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

need a nil check for ext.Value ?


// VerifyF9Cert will ensure that the user is using a Yubikey that was provisioned to him or her,
// rather than just any Yubikey.
func VerifyF9Cert(f9CertDirPath string, f9Cert *x509.Certificate) error {
Copy link
Collaborator

Choose a reason for hiding this comment

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

does it need to be public?

// Name is a unique name to identify an authentication module.
Name = "f9_verify"

modHexMap = "cbdefghijklnrtuv"
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: modHexMap is defined twice in two different packages.

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.

3 participants