Mailing List Archive

How should SSHSIG/ssh-keygen handle signatures by a Certificate
Heyya openssh-unix-dev,

Apologies in advance for what may be the weirdest email of the year so
far on this list (a low bar, to be fair).

I've been playing a bit with OpenSSH SSHKEYs and operationalizing them
with toy proofs of concepts, trying to better understand limits
on how to properly use them and design real code accordingly.

As such, I've hit a boundary condition that I would love to get some feedback
from the list on.

I've constructed an SSHSIG signature over a simple file ('text' namespace), but
using an SSH Certificate, rather than a plain public key. Some example files
can be found at the bottom[examples].

My questions going in:

1. Will it accept an SSH certificate at all?
2. If it does, will it accept the underlying Public Key too?
3. What happens if my principals in the SSHSIG principals file don't
match in the ssh certificate
4. What happens if the certificate is expired? SSHSIG has no
'time of signature', so how do we even know? Now?

The answers according to OpenSSH 9.1 / git

1. Yes!
2. No!
3/4. No validation on the Certificate is done

I have *no* idea what the best way to solve this is, but I see a few possible
options I'd love to get some feedback from the list on:

1. Can anyone think of a valid reason *at all* to accept SSH Certificates
in an SSHSIG?

IF YES: How are you validating? This would likely require custom validation to
check the Certificate, or generate some sort of Principals file in a
real-time way using all known Certificates issued by the CA. What is
the use-case? Is ssh-keygen working in a way you're happy with?

If I were forced to make it work, I'd dump all the issued Certificates
by a CA, and list them in the principals file with matching principals
and valid-* constraints matching the issued Certificate exactly. Maybe
even do it by CA keys, and apend certs to the file as I see them? I'm
not sure. I'm having a hard time with this one. How do you know what
the signature time is?

IF NO: Should we discourage this construction in the SSHSIG spec and add a
note to implementers to avoid validating this pattern, or creating such
signatures? I don't know if this is a MAY NOT, SHOULD NOT or MUST NOT
situation if it becomes discouraged. My gut tells me I would likely do
this is a way that looks like SHOULD NOT, since I guess it *is*
possible to do this on your own if you implement the SSHSIG spec
yourself, and validate without using the principals file, rather
trusting signatures by seeding the trusted CAs.

Either way, this brings up the next question:

2. Can anyone think of a valid reason *in ssh-keygen* to accept SSH Certificates
in an SSHSIG?

IF YES: How should the logic get cleaned up (if at all)? - at minimum, I'd
expect the time to get validated since everything else was provided
explicitly by the user in the principals file. I patched ssh-keygen
locally[1] to play with how hard this would be, it wasn't bad but it
did require some duplicate logic (as originally seen in
'sshkey_cert_check_authority') on the validity time. I don't like this
patch and it shouldn't be accepted, as-is, mostly because I think
there's a *lot* more work than this involved, and a few other codepaths
for validation in 'sig_*' that aren't included with that diff. Just
there for discussion more than anything else.

Relatedly, I have *no* idea what the expected behavior becomes when you
set valid-after=timestamp / valid-before=timestamp in the principals
file *in addition* to the Certificate having a
valid-before/valid-after. I suspect all constrants would have to be met
(which is to say, it becomes the latest valid-after and earliest
valid-before of all constraints in the cert and principals file
combined)

Even if we fix the valid time stuff, I have no idea what to do about
the principals. The only thing that comes to mind is to ensure that the
principal passed to 'sig_verify' is in the certificate principals in
addition to the principals file ssh-genkey was given (so wildcards
behave as expected, etc).

I'm reluctant to validate the Certificate's signing key against the
sshsig principals file (by some creative use of
'sshkey_cert_check_authority'), since they're not trusted SSH CAs, so
trusting their Certificates seems like a mistake to me.

Perhaps pulling the public key out of the Certificate in the SSHSIG and
using that to validate? Smells like a dangerous pattern, since there's
a lot of constrants on the key usage (principals, validity, usages, key
type, extensions -- at minimum) that would get dropped for validation
[intentionally this time].

What do others think here?

IF NO: I patched ssh-keygen locally to play with this[2]. This would allow such
constructions to exist, and even makes no effort to stop someone who's
tricked ssh-keygen to create such signatures (I generated these
signatures by hand, I'm not sure how to get ssh-keygen to create them
yet - although I'm sure it's possible, I'm just not a smart man), but it
will give the user an error when *validating* the Certificates.
Hopefully this check wouldn't impact anyone in the wild? If so I would
love to learn a bit more about that use case.

Example CLI outputs:

Certificate (as seen in 'ssh-keygen -L')
Type: ssh-ed25519-cert-v01@openssh.com user certificate
Public key: ED25519-CERT SHA256:4HTjfBTMZ31VWFfpQLTCn3QHeV7llCl0gDl47SLIpTU
Signing CA: ED25519 SHA256:4HTjfBTMZ31VWFfpQLTCn3QHeV7llCl0gDl47SLIpTU (using ssh-ed25519)
Key ID: "test key"
Serial: 1
Valid: from 2023-01-19T09:51:58 to 2023-01-19T09:52:28
Principals:
me@mydomain.fqdn
Critical Options: (none)
Extensions: (none)

Current OpenSSH as provided by my OS:

```
$ ssh-keygen -Y verify -I test2 -f principals -n text -s hello.asc < hello
Good "text" signature for test2 with ED25519-CERT key SHA256:4HTjfBTMZ31VWFfpQLTCn3QHeV7llCl0gDl47SLIpTU
```

As-is using the underlying ed25519 public key from the Certificate (which is
to say the whole Certificate needs to be in the principals file, not the
public key from the Certificate, tbh - as expected - this is likely the
least surprising one).

```
$ ssh-keygen -Y verify -I test1 -f principals -n text -s hello.asc < hello
Could not verify signature.
```

With the verify-time patch:

```
$ ssh-keygen -Y verify -O verify-time=20230119095000 -I test2 -f principals -n text -s hello.asc < hello
Could not verify signature.
$ ssh-keygen -Y verify -O verify-time=20230119095200 -I test2 -f principals -n text -s hello.asc < hello
Good "text" signature for test2 with ED25519-CERT key SHA256:4HTjfBTMZ31VWFfpQLTCn3QHeV7llCl0gDl47SLIpTU
$ ssh-keygen -Y verify -O verify-time=20230119095900 -I test2 -f principals -n text -s hello.asc < hello
Could not verify signature.
```

With the deny Certificates patch:

```
$ ssh-keygen -Y verify -I test2 -f principals -n text -s hello.asc < hello
sig_verify: ssh certificates are not supported in sshsig
Could not verify signature.
```

[1]: verify-time patch

```
diff --git a/ssh-keygen.c b/ssh-keygen.c
index ae05440f..716e9486 100644
--- a/ssh-keygen.c
+++ b/ssh-keygen.c
@@ -2815,6 +2815,18 @@ sig_verify(const char *signature, const char *sig_namespace,
}
}

+ if (verify_time && sshkey_is_cert(sign_key)) {
+ /* If -O verify-time is set, validate the Certificate too */
+ if (verify_time <= sign_key->cert->valid_after) {
+ debug3_fr(r, "Certificate invalid: not yet valid");
+ goto done;
+ }
+ if (verify_time >= sign_key->cert->valid_before) {
+ debug3_fr(r, "Certificate invalid: expired");
+ goto done;
+ }
+ }
+
if (allowed_keys != NULL && (r = sshsig_check_allowed_keys(allowed_keys,
sign_key, principal, sig_namespace, verify_time)) != 0) {
debug3_fr(r, "sshsig_check_allowed_keys");
```

[2]: deny Certificates

```
diff --git a/ssh-keygen.c b/ssh-keygen.c
index ae05440f..ce1e1c29 100644
--- a/ssh-keygen.c
+++ b/ssh-keygen.c
@@ -2815,6 +2815,11 @@ sig_verify(const char *signature, const char *sig_namespace,
}
}

+ if (sshkey_is_cert(sign_key)) {
+ error_f("ssh certificates are not supported in sshsig");
+ goto done;
+ }
+
if (allowed_keys != NULL && (r = sshsig_check_allowed_keys(allowed_keys,
sign_key, principal, sig_namespace, verify_time)) != 0) {
debug3_fr(r, "sshsig_check_allowed_keys");
@@ -2883,6 +2888,10 @@ sig_find_principals(const char *signature, const char *allowed_keys,
error_fr(r, "sshsig_get_pubkey");
goto done;
}
+ if (sshkey_is_cert(sign_key)) {
+ error_f("ssh certificates are not supported in sshsig");
+ goto done;
+ }
if ((r = sshsig_find_principals(allowed_keys, sign_key,
verify_time, &principals)) != 0) {
if (r != SSH_ERR_KEY_NOT_FOUND)
```

[examples]:

'hello' file:
```
no one actually looks at this do they
```

'hello.asc' file:
```
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAAUYAAAAgc3NoLWVkMjU1MTktY2VydC12MDFAb3BlbnNzaC5j
b20AAAAgttJe6gKGMeH+MNY6L3Ip0BuJnZebPvkuW6Zl9ZJs35wAAAAggO3sKO3l
Z/uhRpVlFNbxnoLG09Xx7PEcUKtootiXWm0AAAAAAAAAAQAAAAEAAAAIdGVzdCBr
ZXkAAAAUAAAAEG1lQG15ZG9tYWluLmZxZG4AAAAAY8lZDgAAAABjyVksAAAAAAAA
AAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACCA7ewo7eVn+6FGlWUU1vGegsbT
1fHs8RxQq2ii2JdabQAAAFMAAAALc3NoLWVkMjU1MTkAAABAiAoeiwtDINpEZAWl
tfqG+MaxBk4YLZcUiDQKblxgBy2bqc52tLQE24jn4LKj96ZxHNITNBdvY0fODMiA
dBggAQAAAAR0ZXh0AAAAAAAAAAZzaGEyNTYAAABTAAAAC3NzaC1lZDI1NTE5AAAA
QEwhhhboXmBwfFEh5+ztPUfjVtPTvzboCZ/VBQyUvBsEitSZuoC1MrDLCP54qkRX
/uSGRXWwZEWLL+SJeeJWTQ8=
-----END SSH SIGNATURE-----
```

'principals' file:
```
test1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIDt7Cjt5Wf7oUaVZRTW8Z6CxtPV8ezxHFCraKLYl1pt
test2 ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAILbSXuoChjHh/jDWOi9yKdAbiZ2Xmz75LlumZfWSbN+cAAAAIIDt7Cjt5Wf7oUaVZRTW8Z6CxtPV8ezxHFCraKLYl1ptAAAAAAAAAAEAAAABAAAACHRlc3Qga2V5AAAAFAAAABBtZUBteWRvbWFpbi5mcWRuAAAAAGPJWQ4AAAAAY8lZLAAAAAAAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAggO3sKO3lZ/uhRpVlFNbxnoLG09Xx7PEcUKtootiXWm0AAABTAAAAC3NzaC1lZDI1NTE5AAAAQIgKHosLQyDaRGQFpbX6hvjGsQZOGC2XFIg0Cm5cYActm6nOdrS0BNuI5+Cyo/emcRzSEzQXb2NHzgzIgHQYIAE=
```

paultag

--
:wq
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
Re: How should SSHSIG/ssh-keygen handle signatures by a Certificate [ In reply to ]
On 10/02/2023 04:14, Paul Tagliamonte wrote:
> My questions going in:
>
> 1. Will it accept an SSH certificate at all?
> 2. If it does, will it accept the underlying Public Key too?
> 3. What happens if my principals in the SSHSIG principals file don't
> match in the ssh certificate
> 4. What happens if the certificate is expired? SSHSIG has no
> 'time of signature', so how do we even know? Now?
>
> The answers according to OpenSSH 9.1 / git
>
> 1. Yes!
> 2. No!
> 3/4. No validation on the Certificate is done

You didn't provide the "-Y sign" commands that you used, but let me
demonstrate using OpenSSH_9.2p1.  For testing I have:

~/.ssh/id_rsa
~/.ssh/id_rsa.pub
~/.ssh/id_rsa-cert.pub    # an expired certificate containing various
principals, one of them is "brian@+nsrc"

I do not have the CA private key available locally - the certificate was
signed using Hashicorp Vault - but I do have its public key.

As you described, plain signing works:

$ echo "hello" >hello
$ ssh-keygen -Y sign -f ~/.ssh/id_rsa.pub -n file hello
Signing file hello
Write signature to hello.sig
$ mv hello.sig hello.sig1
$ echo "dontcare $(cat ~/.ssh/id_rsa.pub)" >hello.allowed1
$ ssh-keygen -Y verify -f hello.allowed1 -n file -s hello.sig1 -I
dontcare <hello
Good "file" signature for dontcare with RSA key
SHA256:mVV81jWVCP/SDRFA7vRM/SDQniylCAcBoSERWyhAXEo

I think you probably tried to do a certificate signature creation and
verification like this:

$ ssh-keygen -Y sign -f ~/.ssh/id_rsa-cert.pub -n file hello
Signing file hello
Write signature to hello.sig
$ mv hello.sig hello.sig2
$ echo "brian@+nsrc $(cat ~/.ssh/id_rsa.pub)" >hello.allowed2
$ ssh-keygen -Y verify -f hello.allowed2 -n file -s hello.sig2 -I
brian@+nsrc <hello
Could not verify signature.

Note that the *signing* process was successful.  This implies that it
was using the private key id_rsa to make the signature (since the CA's
private key is nowhere).

Note also that the second signature is about twice the size of the
first, although both have a single "BEGIN SSH SIGNATURE" block:

$ ls -l hello.sig1 hello.sig2
-rw-r--r--  1 brian  staff   866 10 Feb 08:30 hello.sig1
-rw-r--r--  1 brian  staff  1653 10 Feb 08:42 hello.sig2

In other words, it's a chain. You need to verify it using the CA's
public key:

$ echo "brian@+nsrc *cert-authority $(cat ca.pub)*" >hello.allowed3
$ ssh-keygen -Y verify -f hello.allowed3 -n file -s hello.sig2 -I
brian@+nsrc <hello
hello.allowed3:1: certificate not authorized: Certificate invalid: expired
Could not verify signature.
$ ssh-keygen -Y match-principals -f hello.allowed3 -n file -s hello.sig2
-I brian@+nsrc <hello
brian@+nsrc

To summarize, I believe the following is true:

- to verify a detached signature made using a certificate, you must
provide the public key of the certificate authority which originally
signed that certificate
- the time validity of the parent certificate is already being verified,
without any code patches required

I guess it might be more user friendly not to allow signing with an
expired or not-yet-valid certificate, or at least to warn if you do this.

Regarding your other question:

"3. What happens if my principals in the SSHSIG principals file don't match in the ssh certificate"

The answer is the manpage under "ALLOWED SIGNERS":

     When verifying signatures made by certificates, the expected
principal name must match
     both the principals pattern in the allowed signers file and the
principals embedded in
     the certificate itself.

Regards,

Brian.
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
Re: How should SSHSIG/ssh-keygen handle signatures by a Certificate [ In reply to ]
On Fri, Feb 10, 2023 at 09:10:04AM +0000, Brian Candler wrote:
> You didn't provide the "-Y sign" commands that you used, but let me
> demonstrate using OpenSSH_9.2p1. For testing I have:

Well, part of that is I didn't use -Y sign :) - from my original email:

> I generated these signatures by hand, I'm not sure how to get
> ssh-keygen to create them yet - although I'm sure it's possible,
> I'm just not a smart man

OK, well, by hand is perhaps unhelpful. I implemented SSHSIG myself[1]
and generated signatures using that implementation.

> In other words, it's a chain. You need to verify it using the CA's
> public key:
>
> $ echo "brian@+nsrc cert-authority $(cat ca.pub)" >hello.allowed3

I think `cert-authority` is the critical bit I was missing in my
example. When I add that, I get a more interesting error:

```
$ ssh-keygen -Y verify -I test1 -f principals -n text -s hello.asc < hello
principals:1: certificate not authorized: Certificate invalid: expired
Could not verify signature.
```

However, when I fix the time:

```
$ ssh-keygen -Y verify -O verify-time=20230119095200 -I test1 -f principals -n text -s hello.asc < hello
principals:1: certificate not authorized: Certificate invalid: name is not a listed principal
```

Which is now even more interesting and much closer to what I was
expecting, which is great.

> To summarize, I believe the following is true:
>
> - to verify a detached signature made using a certificate, you must
> provide the public key of the certificate authority which originally
> signed that certificate

Yeah, I believe this to be true as well -- the missing bit was a typo
excluding 'cert-authority' in my principals file from earlier, which is
plain user error.

> - the time validity of the parent certificate is already being
> verified, without any code patches required

I agree. -O verify-time also works as exected here.

> I guess it might be more user friendly not to allow signing with an
> expired or not-yet-valid certificate, or at least to warn if you do
> this.
>
> Regarding your other question:
> "3. What happens if my principals in the SSHSIG principals file don't match in
> the ssh certificate"
>
> The answer is the manpage under "ALLOWED SIGNERS":
>
> When verifying signatures made by certificates, the expected
> principal name must match
> both the principals pattern in the allowed signers file and the
> principals embedded in
> the certificate itself.

I'll admit I also missed this (well, I saw it, but I globbed this into
the ssh certificates principals file for logging into a system rather
than fully internalizing the 'signature' bit there), it's a very helpful
note.

I can confirm this above as well. The expected behavior also happens
when setting valid-before/valid-after in the principals file in addition
to the Certificate, which also makes a great deal of sense.



Right, alright. Sorry for the noise, specifically, `cert-authority` not
being in the test principals file was my downfall here. This helps me a
great deal get back the the original task, which was to clean up the
validation logic in the aforementioned go code.

As usual, it's not a shocker to find corner cases well addressed, I'm
just sorry I didn't spot it sooner.

Thanks, Brian!
paultag

--
:wq
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev