| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162 |
- /*
- * Public key type for OpenSSH certificates.
- */
- #include "ssh.h"
- #include "putty.h"
- enum {
- SSH_CERT_TYPE_USER = 1,
- SSH_CERT_TYPE_HOST = 2,
- };
- typedef struct opensshcert_key {
- strbuf *nonce;
- uint64_t serial;
- uint32_t type;
- strbuf *key_id;
- strbuf *valid_principals;
- uint64_t valid_after, valid_before;
- strbuf *critical_options;
- strbuf *extensions;
- strbuf *reserved;
- strbuf *signature_key;
- strbuf *signature;
- ssh_key *basekey;
- ssh_key sshk;
- } opensshcert_key;
- typedef struct blob_fmt {
- const unsigned *fmt;
- size_t len;
- } blob_fmt;
- typedef struct opensshcert_extra {
- /*
- * OpenSSH certificate formats aren't completely consistent about
- * the relationship between the public+private blob uploaded to
- * the agent for the certified key type, and the one for the base
- * key type. Here we specify the mapping.
- *
- * Each of these foo_fmt strings indicates the layout of a
- * particular version of the key, in the form of an array of
- * integers together with a length, with each integer describing
- * one of the components of the key. The integers are defined by
- * enums, so that they're tightly packed; the general idea is that
- * if you're converting from one form to another, then you use the
- * format list for the source format to read out a succession of
- * SSH strings from the source data and put them in an array
- * indexed by the integer ids, and then use the list for the
- * destination format to write the strings out to the destination
- * in the right (maybe different) order.
- *
- * pub_fmt describes the format of the public-key blob for the
- * base key type, not counting the initial string giving the key
- * type identifier itself. As far as I know, this always matches
- * the format of the public-key data appearing in the middle of
- * the certificate.
- *
- * base_ossh_fmt describes the format of the full OpenSSH blob
- * appearing in the ssh-agent protocol for the base key,
- * containing the public and private key data.
- *
- * cert_ossh_fmt describes the format of the OpenSSH blob for the
- * certificate key format, beginning just after the certificate
- * string itself.
- */
- blob_fmt pub_fmt, base_ossh_fmt, cert_ossh_fmt;
- /*
- * The RSA-SHA2 algorithm names have their SSH id set to names
- * like "rsa-sha2-512-cert-...", which is what will be received in
- * the KEXINIT algorithm list if a host key in one of those
- * algorithms is presented. But the _key_ type id that will appear
- * in the public key blob is "ssh-rsa-cert-...". So we need a
- * separate field to indicate the key type id we expect to see in
- * certified public keys, and also the one we want to put back
- * into the artificial public blob we make to pass to the
- * constructor for the underlying key.
- *
- * (In rsa.c this is managed much more simply, because everything
- * sharing the same vtable wants the same key type id.)
- */
- const char *cert_key_ssh_id, *base_key_ssh_id;
- } opensshcert_extra;
- /*
- * The actual integer arrays defining the per-key blob formats.
- */
- /* DSA is the most orthodox: only the obviously necessary public key
- * info appears at all, it's in the same order everywhere, and none of
- * it is repeated unnecessarily */
- enum { DSA_p, DSA_q, DSA_g, DSA_y, DSA_x };
- static const unsigned dsa_pub_fmt[] = { DSA_p, DSA_q, DSA_g, DSA_y };
- static const unsigned dsa_base_ossh_fmt[] = {
- DSA_p, DSA_q, DSA_g, DSA_y, DSA_x };
- static const unsigned dsa_cert_ossh_fmt[] = { DSA_x };
- /* ECDSA is almost as nice, except that it pointlessly mentions the
- * curve name in the public data, which shouldn't be necessary given
- * that the SSH key id has already implied it. But at least that's
- * consistent everywhere. */
- enum { ECDSA_curve, ECDSA_point, ECDSA_exp };
- static const unsigned ecdsa_pub_fmt[] = { ECDSA_curve, ECDSA_point };
- static const unsigned ecdsa_base_ossh_fmt[] = {
- ECDSA_curve, ECDSA_point, ECDSA_exp };
- static const unsigned ecdsa_cert_ossh_fmt[] = { ECDSA_exp };
- /* Ed25519 has the oddity that the private data following the
- * certificate in the OpenSSH blob is preceded by an extra copy of the
- * public data, for no obviously necessary reason since that doesn't
- * happen in any of the rest of these formats */
- enum { EDDSA_point, EDDSA_exp };
- static const unsigned eddsa_pub_fmt[] = { EDDSA_point };
- static const unsigned eddsa_base_ossh_fmt[] = { EDDSA_point, EDDSA_exp };
- static const unsigned eddsa_cert_ossh_fmt[] = { EDDSA_point, EDDSA_exp };
- /* And RSA has the quirk that the modulus and exponent are reversed in
- * the base key type's OpenSSH blob! */
- enum { RSA_e, RSA_n, RSA_d, RSA_p, RSA_q, RSA_iqmp };
- static const unsigned rsa_pub_fmt[] = { RSA_e, RSA_n };
- static const unsigned rsa_base_ossh_fmt[] = {
- RSA_n, RSA_e, RSA_d, RSA_p, RSA_q, RSA_iqmp };
- static const unsigned rsa_cert_ossh_fmt[] = { RSA_d, RSA_p, RSA_q, RSA_iqmp };
- /*
- * Routines to transform one kind of blob into another based on those
- * foo_fmt integer arrays.
- */
- typedef struct BlobTransformer {
- ptrlen *parts;
- size_t nparts;
- } BlobTransformer;
- #define BLOBTRANS_DECLARE(bt) BlobTransformer bt[1] = { { NULL, 0 } }
- static inline void blobtrans_clear(BlobTransformer *bt)
- {
- sfree(bt->parts);
- bt->parts = NULL;
- bt->nparts = 0;
- }
- static inline bool blobtrans_read(BlobTransformer *bt, BinarySource *src,
- blob_fmt blob)
- {
- size_t nparts = bt->nparts;
- for (size_t i = 0; i < blob.len; i++)
- if (nparts < blob.fmt[i]+1)
- nparts = blob.fmt[i]+1;
- if (nparts > bt->nparts) {
- bt->parts = sresize(bt->parts, nparts, ptrlen);
- while (bt->nparts < nparts)
- bt->parts[bt->nparts++] = make_ptrlen(NULL, 0);
- }
- for (size_t i = 0; i < blob.len; i++) {
- size_t j = blob.fmt[i];
- ptrlen part = get_string(src);
- if (bt->parts[j].ptr) {
- /*
- * If the same string appears in both the public blob and
- * the private data, check they match. (This happens in
- * Ed25519: an extra copy of the public point string
- * appears in the certified OpenSSH data after the
- * certificate and before the private key.)
- */
- if (!ptrlen_eq_ptrlen(bt->parts[j], part))
- return false;
- }
- bt->parts[j] = part;
- }
- return true;
- }
- static inline void blobtrans_write(BlobTransformer *bt, BinarySink *bs,
- blob_fmt blob)
- {
- for (size_t i = 0; i < blob.len; i++) {
- assert(i < bt->nparts);
- ptrlen part = bt->parts[blob.fmt[i]];
- assert(part.ptr);
- put_stringpl(bs, part);
- }
- }
- /*
- * Forward declarations.
- */
- static ssh_key *opensshcert_new_pub(const ssh_keyalg *self, ptrlen pub);
- static ssh_key *opensshcert_new_priv(
- const ssh_keyalg *self, ptrlen pub, ptrlen priv);
- static ssh_key *opensshcert_new_priv_openssh(
- const ssh_keyalg *self, BinarySource *src);
- static void opensshcert_freekey(ssh_key *key);
- static char *opensshcert_invalid(ssh_key *key, unsigned flags);
- static void opensshcert_sign(ssh_key *key, ptrlen data, unsigned flags,
- BinarySink *bs);
- static bool opensshcert_verify(ssh_key *key, ptrlen sig, ptrlen data);
- static void opensshcert_public_blob(ssh_key *key, BinarySink *bs);
- static void opensshcert_private_blob(ssh_key *key, BinarySink *bs);
- static void opensshcert_openssh_blob(ssh_key *key, BinarySink *bs);
- static void opensshcert_ca_public_blob(ssh_key *key, BinarySink *bs);
- static void opensshcert_cert_id_string(ssh_key *key, BinarySink *bs);
- static SeatDialogText *opensshcert_cert_info(ssh_key *key);
- static bool opensshcert_has_private(ssh_key *key);
- static char *opensshcert_cache_str(ssh_key *key);
- static key_components *opensshcert_components(ssh_key *key);
- static ssh_key *opensshcert_base_key(ssh_key *key);
- static bool opensshcert_check_cert(
- ssh_key *key, bool host, ptrlen principal, uint64_t time,
- const ca_options *opts, BinarySink *error);
- static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob);
- static unsigned opensshcert_supported_flags(const ssh_keyalg *self);
- static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self,
- unsigned flags);
- static char *opensshcert_alg_desc(const ssh_keyalg *self);
- static bool opensshcert_variable_size(const ssh_keyalg *self);
- static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self,
- const ssh_keyalg *base);
- /*
- * Top-level vtables for the certified key formats, defined via a list
- * macro so I can also make an array of them all.
- */
- #define KEYALG_LIST(X) \
- X(ssh_dsa, "ssh-dss", "ssh-dss", dsa) \
- X(ssh_rsa, "ssh-rsa", "ssh-rsa", rsa) \
- X(ssh_rsa_sha256, "rsa-sha2-256", "ssh-rsa", rsa) \
- X(ssh_rsa_sha512, "rsa-sha2-512", "ssh-rsa", rsa) \
- X(ssh_ecdsa_ed25519, "ssh-ed25519", "ssh-ed25519", eddsa) \
- X(ssh_ecdsa_nistp256, "ecdsa-sha2-nistp256","ecdsa-sha2-nistp256", ecdsa) \
- X(ssh_ecdsa_nistp384, "ecdsa-sha2-nistp384","ecdsa-sha2-nistp384", ecdsa) \
- X(ssh_ecdsa_nistp521, "ecdsa-sha2-nistp521","ecdsa-sha2-nistp521", ecdsa) \
- /* end of list */
- #define KEYALG_DEF(name, ssh_alg_id_prefix, ssh_key_id_prefix, fmt_prefix) \
- static const struct opensshcert_extra opensshcert_##name##_extra = { \
- .pub_fmt = { .fmt = fmt_prefix ## _pub_fmt, \
- .len = lenof(fmt_prefix ## _pub_fmt) }, \
- .base_ossh_fmt = { .fmt = fmt_prefix ## _base_ossh_fmt, \
- .len = lenof(fmt_prefix ## _base_ossh_fmt) }, \
- .cert_ossh_fmt = { .fmt = fmt_prefix ## _cert_ossh_fmt, \
- .len = lenof(fmt_prefix ## _cert_ossh_fmt) }, \
- .cert_key_ssh_id = ssh_key_id_prefix "[email protected]", \
- .base_key_ssh_id = ssh_key_id_prefix, \
- }; \
- \
- const ssh_keyalg opensshcert_##name = { \
- .new_pub = opensshcert_new_pub, \
- .new_priv = opensshcert_new_priv, \
- .new_priv_openssh = opensshcert_new_priv_openssh, \
- .freekey = opensshcert_freekey, \
- .invalid = opensshcert_invalid, \
- .sign = opensshcert_sign, \
- .verify = opensshcert_verify, \
- .public_blob = opensshcert_public_blob, \
- .private_blob = opensshcert_private_blob, \
- .openssh_blob = opensshcert_openssh_blob, \
- .has_private = opensshcert_has_private, \
- .cache_str = opensshcert_cache_str, \
- .components = opensshcert_components, \
- .base_key = opensshcert_base_key, \
- .ca_public_blob = opensshcert_ca_public_blob, \
- .check_cert = opensshcert_check_cert, \
- .cert_id_string = opensshcert_cert_id_string, \
- .cert_info = opensshcert_cert_info, \
- .pubkey_bits = opensshcert_pubkey_bits, \
- .supported_flags = opensshcert_supported_flags, \
- .alternate_ssh_id = opensshcert_alternate_ssh_id, \
- .alg_desc = opensshcert_alg_desc, \
- .variable_size = opensshcert_variable_size, \
- .related_alg = opensshcert_related_alg, \
- .ssh_id = ssh_alg_id_prefix "[email protected]", \
- .cache_id = "opensshcert-" ssh_key_id_prefix, \
- .extra = &opensshcert_##name##_extra, \
- .is_certificate = true, \
- .base_alg = &name, \
- };
- KEYALG_LIST(KEYALG_DEF)
- #undef KEYALG_DEF
- #define KEYALG_LIST_ENTRY(name, algid, keyid, fmt) &opensshcert_##name,
- static const ssh_keyalg *const opensshcert_all_keyalgs[] = {
- KEYALG_LIST(KEYALG_LIST_ENTRY)
- };
- #undef KEYALG_LIST_ENTRY
- static strbuf *get_base_public_blob(BinarySource *src,
- const opensshcert_extra *extra)
- {
- strbuf *basepub = strbuf_new();
- put_stringz(basepub, extra->base_key_ssh_id);
- /* Make the base public key blob out of the public key
- * material in the certificate. This invocation of the
- * blobtrans system doesn't do any format translation, but it
- * does ensure that the right amount of data is copied so that
- * src ends up in the right position to read the remaining
- * certificate fields. */
- BLOBTRANS_DECLARE(bt);
- blobtrans_read(bt, src, extra->pub_fmt);
- blobtrans_write(bt, BinarySink_UPCAST(basepub), extra->pub_fmt);
- blobtrans_clear(bt);
- return basepub;
- }
- static opensshcert_key *opensshcert_new_shared(
- const ssh_keyalg *self, ptrlen blob, strbuf **basepub_out)
- {
- const opensshcert_extra *extra = self->extra;
- BinarySource src[1];
- BinarySource_BARE_INIT_PL(src, blob);
- /* Check the initial key-type string */
- if (!ptrlen_eq_string(get_string(src), extra->cert_key_ssh_id))
- return NULL;
- opensshcert_key *ck = snew(opensshcert_key);
- memset(ck, 0, sizeof(*ck));
- ck->sshk.vt = self;
- ck->nonce = strbuf_dup(get_string(src));
- strbuf *basepub = get_base_public_blob(src, extra);
- ck->serial = get_uint64(src);
- ck->type = get_uint32(src);
- ck->key_id = strbuf_dup(get_string(src));
- ck->valid_principals = strbuf_dup(get_string(src));
- ck->valid_after = get_uint64(src);
- ck->valid_before = get_uint64(src);
- ck->critical_options = strbuf_dup(get_string(src));
- ck->extensions = strbuf_dup(get_string(src));
- ck->reserved = strbuf_dup(get_string(src));
- ck->signature_key = strbuf_dup(get_string(src));
- ck->signature = strbuf_dup(get_string(src));
- if (get_err(src)) {
- ssh_key_free(&ck->sshk);
- strbuf_free(basepub);
- return NULL;
- }
- *basepub_out = basepub;
- return ck;
- }
- static ssh_key *opensshcert_new_pub(const ssh_keyalg *self, ptrlen pub)
- {
- strbuf *basepub;
- opensshcert_key *ck = opensshcert_new_shared(self, pub, &basepub);
- if (!ck)
- return NULL;
- ck->basekey = ssh_key_new_pub(self->base_alg, ptrlen_from_strbuf(basepub));
- strbuf_free(basepub);
- if (!ck->basekey) {
- ssh_key_free(&ck->sshk);
- return NULL;
- }
- return &ck->sshk;
- }
- static ssh_key *opensshcert_new_priv(
- const ssh_keyalg *self, ptrlen pub, ptrlen priv)
- {
- strbuf *basepub;
- opensshcert_key *ck = opensshcert_new_shared(self, pub, &basepub);
- if (!ck)
- return NULL;
- ck->basekey = ssh_key_new_priv(self->base_alg,
- ptrlen_from_strbuf(basepub), priv);
- strbuf_free(basepub);
- if (!ck->basekey) {
- ssh_key_free(&ck->sshk);
- return NULL;
- }
- return &ck->sshk;
- }
- static ssh_key *opensshcert_new_priv_openssh(
- const ssh_keyalg *self, BinarySource *src)
- {
- const opensshcert_extra *extra = self->extra;
- ptrlen cert = get_string(src);
- strbuf *basepub;
- opensshcert_key *ck = opensshcert_new_shared(self, cert, &basepub);
- if (!ck)
- return NULL;
- strbuf *baseossh = strbuf_new();
- /* Make the base OpenSSH key blob out of the public key blob
- * returned from opensshcert_new_shared, and the trailing
- * private data following the certificate */
- BLOBTRANS_DECLARE(bt);
- BinarySource pubsrc[1];
- BinarySource_BARE_INIT_PL(pubsrc, ptrlen_from_strbuf(basepub));
- get_string(pubsrc); /* skip key type id */
- /* blobtrans_read might fail in this case, because we're reading
- * from two sources and they might fail to match */
- bool success = blobtrans_read(bt, pubsrc, extra->pub_fmt) &&
- blobtrans_read(bt, src, extra->cert_ossh_fmt);
- blobtrans_write(bt, BinarySink_UPCAST(baseossh), extra->base_ossh_fmt);
- blobtrans_clear(bt);
- if (!success) {
- ssh_key_free(&ck->sshk);
- strbuf_free(basepub);
- strbuf_free(baseossh);
- return NULL;
- }
- strbuf_free(basepub);
- BinarySource osshsrc[1];
- BinarySource_BARE_INIT_PL(osshsrc, ptrlen_from_strbuf(baseossh));
- ck->basekey = ssh_key_new_priv_openssh(self->base_alg, osshsrc);
- strbuf_free(baseossh);
- if (!ck->basekey) {
- ssh_key_free(&ck->sshk);
- return NULL;
- }
- return &ck->sshk;
- }
- static void opensshcert_freekey(ssh_key *key)
- {
- opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
- /* If this function is called from one of the above constructors
- * because it failed part way through, we might not have managed
- * to construct ck->basekey, so it might be NULL. */
- if (ck->basekey)
- ssh_key_free(ck->basekey);
- strbuf_free(ck->nonce);
- strbuf_free(ck->key_id);
- strbuf_free(ck->valid_principals);
- strbuf_free(ck->critical_options);
- strbuf_free(ck->extensions);
- strbuf_free(ck->reserved);
- strbuf_free(ck->signature_key);
- strbuf_free(ck->signature);
- sfree(ck);
- }
- static ssh_key *opensshcert_base_key(ssh_key *key)
- {
- opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
- return ck->basekey;
- }
- /*
- * Make a public key object from the CA public blob, potentially
- * taking into account that the signature might override the algorithm
- * name
- */
- static ssh_key *opensshcert_ca_pub_key(
- opensshcert_key *ck, ptrlen sig, ptrlen *algname)
- {
- ptrlen ca_keyblob = ptrlen_from_strbuf(ck->signature_key);
- ptrlen alg_source = sig.ptr ? sig : ca_keyblob;
- if (algname)
- *algname = pubkey_blob_to_alg_name(alg_source);
- const ssh_keyalg *ca_alg = pubkey_blob_to_alg(alg_source);
- if (!ca_alg)
- return NULL; /* don't even recognise the certifying key type */
- return ssh_key_new_pub(ca_alg, ca_keyblob);
- }
- static void opensshcert_signature_preimage(opensshcert_key *ck, BinarySink *bs)
- {
- const opensshcert_extra *extra = ck->sshk.vt->extra;
- put_stringz(bs, extra->cert_key_ssh_id);
- put_stringpl(bs, ptrlen_from_strbuf(ck->nonce));
- strbuf *basepub = strbuf_new();
- ssh_key_public_blob(ck->basekey, BinarySink_UPCAST(basepub));
- BinarySource src[1];
- BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(basepub));
- get_string(src); /* skip initial key type string */
- put_data(bs, get_ptr(src), get_avail(src));
- strbuf_free(basepub);
- put_uint64(bs, ck->serial);
- put_uint32(bs, ck->type);
- put_stringpl(bs, ptrlen_from_strbuf(ck->key_id));
- put_stringpl(bs, ptrlen_from_strbuf(ck->valid_principals));
- put_uint64(bs, ck->valid_after);
- put_uint64(bs, ck->valid_before);
- put_stringpl(bs, ptrlen_from_strbuf(ck->critical_options));
- put_stringpl(bs, ptrlen_from_strbuf(ck->extensions));
- put_stringpl(bs, ptrlen_from_strbuf(ck->reserved));
- put_stringpl(bs, ptrlen_from_strbuf(ck->signature_key));
- }
- static void opensshcert_public_blob(ssh_key *key, BinarySink *bs)
- {
- opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
- opensshcert_signature_preimage(ck, bs);
- put_stringpl(bs, ptrlen_from_strbuf(ck->signature));
- }
- static void opensshcert_private_blob(ssh_key *key, BinarySink *bs)
- {
- opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
- ssh_key_private_blob(ck->basekey, bs);
- }
- static void opensshcert_openssh_blob(ssh_key *key, BinarySink *bs)
- {
- opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
- const opensshcert_extra *extra = key->vt->extra;
- strbuf *cert = strbuf_new();
- ssh_key_public_blob(key, BinarySink_UPCAST(cert));
- put_stringsb(bs, cert);
- strbuf *baseossh = strbuf_new_nm();
- ssh_key_openssh_blob(ck->basekey, BinarySink_UPCAST(baseossh));
- BinarySource basesrc[1];
- BinarySource_BARE_INIT_PL(basesrc, ptrlen_from_strbuf(baseossh));
- BLOBTRANS_DECLARE(bt);
- blobtrans_read(bt, basesrc, extra->base_ossh_fmt);
- blobtrans_write(bt, bs, extra->cert_ossh_fmt);
- blobtrans_clear(bt);
- strbuf_free(baseossh);
- }
- static void opensshcert_ca_public_blob(ssh_key *key, BinarySink *bs)
- {
- opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
- put_datapl(bs, ptrlen_from_strbuf(ck->signature_key));
- }
- static void opensshcert_cert_id_string(ssh_key *key, BinarySink *bs)
- {
- opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
- put_datapl(bs, ptrlen_from_strbuf(ck->key_id));
- }
- static bool opensshcert_has_private(ssh_key *key)
- {
- opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
- return ssh_key_has_private(ck->basekey);
- }
- static char *opensshcert_cache_str(ssh_key *key)
- {
- opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
- return ssh_key_cache_str(ck->basekey);
- }
- static void opensshcert_time_to_iso8601(BinarySink *bs, uint64_t time)
- {
- time_t t = time;
- char buf[256];
- put_data(bs, buf, strftime(buf, sizeof(buf),
- "%Y-%m-%d %H:%M:%S UTC", gmtime(&t)));
- }
- static void opensshcert_string_list_key_components(
- key_components *kc, strbuf *input, const char *title, const char *title2)
- {
- BinarySource src[1];
- BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(input));
- const char *titles[2] = { title, title2 };
- size_t ntitles = (title2 ? 2 : 1);
- unsigned index = 0;
- while (get_avail(src)) {
- for (size_t ti = 0; ti < ntitles; ti++) {
- ptrlen value = get_string(src);
- if (get_err(src))
- break;
- char *name = dupprintf("%s_%u", titles[ti], index);
- key_components_add_text_pl(kc, name, value);
- sfree(name);
- }
- index++;
- }
- }
- static key_components *opensshcert_components(ssh_key *key)
- {
- opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
- key_components *kc = ssh_key_components(ck->basekey);
- key_components_add_binary(kc, "cert_nonce", ptrlen_from_strbuf(ck->nonce));
- key_components_add_uint(kc, "cert_serial", ck->serial);
- switch (ck->type) {
- case SSH_CERT_TYPE_HOST:
- key_components_add_text(kc, "cert_type", "host");
- break;
- case SSH_CERT_TYPE_USER:
- key_components_add_text(kc, "cert_type", "user");
- break;
- default:
- key_components_add_uint(kc, "cert_type", ck->type);
- break;
- }
- key_components_add_text(kc, "cert_key_id", ck->key_id->s);
- opensshcert_string_list_key_components(kc, ck->valid_principals,
- "cert_valid_principal", NULL);
- key_components_add_uint(kc, "cert_valid_after", ck->valid_after);
- key_components_add_uint(kc, "cert_valid_before", ck->valid_before);
- /* Translate the validity period into human-legible dates, but
- * only if they're not the min/max integer. Rationale: if you see
- * "584554051223-11-09 07:00:15 UTC" as the expiry time you'll be
- * as likely to think it's a weird buffer overflow as half a
- * trillion years in the future! */
- if (ck->valid_after != 0) {
- strbuf *date = strbuf_new();
- opensshcert_time_to_iso8601(BinarySink_UPCAST(date), ck->valid_after);
- key_components_add_text_pl(kc, "cert_valid_after_date",
- ptrlen_from_strbuf(date));
- strbuf_free(date);
- }
- if (ck->valid_before != 0xFFFFFFFFFFFFFFFF) {
- strbuf *date = strbuf_new();
- opensshcert_time_to_iso8601(BinarySink_UPCAST(date), ck->valid_before);
- key_components_add_text_pl(kc, "cert_valid_before_date",
- ptrlen_from_strbuf(date));
- strbuf_free(date);
- }
- opensshcert_string_list_key_components(kc, ck->critical_options,
- "cert_critical_option",
- "cert_critical_option_data");
- opensshcert_string_list_key_components(kc, ck->extensions,
- "cert_extension",
- "cert_extension_data");
- key_components_add_binary(kc, "cert_ca_key", ptrlen_from_strbuf(
- ck->signature_key));
- ptrlen ca_algname;
- ssh_key *ca_key = opensshcert_ca_pub_key(ck, make_ptrlen(NULL, 0),
- &ca_algname);
- key_components_add_text_pl(kc, "cert_ca_key_algorithm_id", ca_algname);
- if (ca_key) {
- key_components *kc_ca_key = ssh_key_components(ca_key);
- for (size_t i = 0; i < kc_ca_key->ncomponents; i++) {
- key_component *comp = &kc_ca_key->components[i];
- char *subname = dupcat("cert_ca_key_", comp->name);
- key_components_add_copy(kc, subname, comp);
- sfree(subname);
- }
- key_components_free(kc_ca_key);
- ssh_key_free(ca_key);
- }
- key_components_add_binary(kc, "cert_ca_sig", ptrlen_from_strbuf(
- ck->signature));
- return kc;
- }
- static SeatDialogText *opensshcert_cert_info(ssh_key *key)
- {
- opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
- SeatDialogText *text = seat_dialog_text_new();
- strbuf *tmp = strbuf_new();
- seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
- "Certificate type");
- switch (ck->type) {
- case SSH_CERT_TYPE_HOST:
- seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
- "host key");
- seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
- "Valid host names");
- break;
- case SSH_CERT_TYPE_USER:
- seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
- "user authentication key");
- seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
- "Valid user names");
- break;
- default:
- seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
- "unknown type %" PRIu32, ck->type);
- seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
- "Valid principals");
- break;
- }
- {
- BinarySource src[1];
- BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(
- ck->valid_principals));
- const char *sep = "";
- strbuf_clear(tmp);
- while (get_avail(src)) {
- ptrlen principal = get_string(src);
- if (get_err(src))
- break;
- put_dataz(tmp, sep);
- sep = ",";
- put_datapl(tmp, principal);
- }
- seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
- "%s", tmp->s);
- }
- seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
- "Validity period");
- strbuf_clear(tmp);
- if (ck->valid_after == 0) {
- if (ck->valid_before == 0xFFFFFFFFFFFFFFFF) {
- put_dataz(tmp, "forever");
- } else {
- put_dataz(tmp, "until ");
- opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp),
- ck->valid_before);
- }
- } else {
- if (ck->valid_before == 0xFFFFFFFFFFFFFFFF) {
- put_dataz(tmp, "after ");
- opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp),
- ck->valid_after);
- } else {
- opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp),
- ck->valid_after);
- put_dataz(tmp, " - ");
- opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp),
- ck->valid_before);
- }
- }
- seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", tmp->s);
- /*
- * List critical options we know about. (This is everything listed
- * in PROTOCOL.certkeys that isn't specific to U2F/FIDO key types
- * that PuTTY doesn't currently support.)
- */
- {
- BinarySource src[1];
- BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(
- ck->critical_options));
- strbuf_clear(tmp);
- while (get_avail(src)) {
- ptrlen key = get_string(src);
- ptrlen value = get_string(src);
- if (get_err(src))
- break;
- if (ck->type == SSH_CERT_TYPE_USER &&
- ptrlen_eq_string(key, "source-address")) {
- BinarySource src2[1];
- BinarySource_BARE_INIT_PL(src2, value);
- ptrlen addresslist = get_string(src2);
- seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
- "Permitted client IP addresses");
- seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
- "%.*s", PTRLEN_PRINTF(addresslist));
- } else if (ck->type == SSH_CERT_TYPE_USER &&
- ptrlen_eq_string(key, "force-command")) {
- BinarySource src2[1];
- BinarySource_BARE_INIT_PL(src2, value);
- ptrlen command = get_string(src2);
- seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
- "Forced remote command");
- seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
- "%.*s", PTRLEN_PRINTF(command));
- }
- }
- }
- /*
- * List certificate extensions. Again, we go through everything in
- * PROTOCOL.certkeys that isn't specific to U2F/FIDO key types.
- * But we also flip the sense round for user-readability: I think
- * it's more likely that the typical key will permit all these
- * things, so we emit no output in that case, and only mention the
- * things that _aren't_ enabled.
- */
- bool x11_ok = false, agent_ok = false, portfwd_ok = false;
- bool pty_ok = false, user_rc_ok = false;
- {
- BinarySource src[1];
- BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(
- ck->extensions));
- while (get_avail(src)) {
- ptrlen key = get_string(src);
- /* ptrlen value = */ get_string(src); // nothing needs this yet
- if (get_err(src))
- break;
- if (ptrlen_eq_string(key, "permit-X11-forwarding")) {
- x11_ok = true;
- } else if (ptrlen_eq_string(key, "permit-agent-forwarding")) {
- agent_ok = true;
- } else if (ptrlen_eq_string(key, "permit-port-forwarding")) {
- portfwd_ok = true;
- } else if (ptrlen_eq_string(key, "permit-pty")) {
- pty_ok = true;
- } else if (ptrlen_eq_string(key, "permit-user-rc")) {
- user_rc_ok = true;
- }
- }
- }
- if (ck->type == SSH_CERT_TYPE_USER) {
- if (!x11_ok) {
- seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
- "X11 forwarding permitted");
- seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
- }
- if (!agent_ok) {
- seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
- "Agent forwarding permitted");
- seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
- }
- if (!portfwd_ok) {
- seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
- "Port forwarding permitted");
- seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
- }
- if (!pty_ok) {
- seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
- "PTY allocation permitted");
- seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
- }
- if (!user_rc_ok) {
- seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
- "Running user ~/.ssh.rc permitted");
- seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
- }
- }
- seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
- "Certificate ID string");
- seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
- "%s", ck->key_id->s);
- seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
- "Certificate serial number");
- seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
- "%" PRIu64, ck->serial);
- char *fp = ssh2_fingerprint_blob(ptrlen_from_strbuf(ck->signature_key),
- SSH_FPTYPE_DEFAULT);
- seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
- "Fingerprint of signing CA key");
- seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp);
- sfree(fp);
- fp = ssh2_fingerprint(key, ssh_fptype_to_cert(SSH_FPTYPE_DEFAULT));
- seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
- "Fingerprint including certificate");
- seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp);
- sfree(fp);
- strbuf_free(tmp);
- return text;
- }
- static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob)
- {
- BinarySource src[1];
- BinarySource_BARE_INIT_PL(src, blob);
- get_string(src); /* key type */
- get_string(src); /* nonce */
- strbuf *basepub = get_base_public_blob(src, self->extra);
- int bits = ssh_key_public_bits(
- self->base_alg, ptrlen_from_strbuf(basepub));
- strbuf_free(basepub);
- return bits;
- }
- static unsigned opensshcert_supported_flags(const ssh_keyalg *self)
- {
- return ssh_keyalg_supported_flags(self->base_alg);
- }
- static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self,
- unsigned flags)
- {
- const char *base_id = ssh_keyalg_alternate_ssh_id(self->base_alg, flags);
- for (size_t i = 0; i < lenof(opensshcert_all_keyalgs); i++) {
- const ssh_keyalg *alg_i = opensshcert_all_keyalgs[i];
- if (!strcmp(base_id, alg_i->base_alg->ssh_id))
- return alg_i->ssh_id;
- }
- return self->ssh_id;
- }
- static char *opensshcert_alg_desc(const ssh_keyalg *self)
- {
- char *base_desc = ssh_keyalg_desc(self->base_alg);
- char *our_desc = dupcat(base_desc, " cert");
- sfree(base_desc);
- return our_desc;
- }
- static bool opensshcert_variable_size(const ssh_keyalg *self)
- {
- return ssh_keyalg_variable_size(self->base_alg);
- }
- static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self,
- const ssh_keyalg *base)
- {
- for (size_t i = 0; i < lenof(opensshcert_all_keyalgs); i++) {
- const ssh_keyalg *alg_i = opensshcert_all_keyalgs[i];
- if (base == alg_i->base_alg)
- return alg_i;
- }
- return self;
- }
- static char *opensshcert_invalid(ssh_key *key, unsigned flags)
- {
- opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
- return ssh_key_invalid(ck->basekey, flags);
- }
- static bool opensshcert_check_cert(
- ssh_key *key, bool host, ptrlen principal, uint64_t time,
- const ca_options *opts, BinarySink *error)
- {
- opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
- bool result = false;
- ssh_key *ca_key = NULL;
- strbuf *preimage = strbuf_new();
- BinarySource src[1];
- ptrlen signature = ptrlen_from_strbuf(ck->signature);
- /*
- * The OpenSSH certificate spec is one-layer only: it explicitly
- * forbids using a certified key in turn as the CA.
- *
- * If it did not, then we'd also have to recursively verify
- * everything up the CA chain until we reached the ultimate root,
- * and then make sure _that_ was something we trusted. (Not to
- * mention that there'd probably be an additional SSH_CERT_TYPE_CA
- * or some such, and certificate options saying what kinds of
- * certificate a CA was trusted to sign for, and ...)
- */
- ca_key = opensshcert_ca_pub_key(ck, make_ptrlen(NULL, 0), NULL);
- if (!ca_key) {
- put_fmt(error, "Certificate's signing key is invalid");
- goto out;
- }
- if (ssh_key_alg(ca_key)->is_certificate) {
- put_fmt(error, "Certificate is signed with a certified key "
- "(forbidden by OpenSSH certificate specification)");
- goto out;
- }
- /*
- * Now re-instantiate the key in a way that matches the signature
- * (i.e. so that if the key is an RSA one we get the right subtype
- * of RSA).
- */
- ssh_key_free(ca_key);
- ca_key = opensshcert_ca_pub_key(ck, signature, NULL);
- if (!ca_key) {
- put_fmt(error, "Certificate's signing key does not match "
- "signature type");
- goto out;
- }
- /* Check which signature algorithm is actually in use, because
- * that might be a reason to reject the certificate (e.g. ssh-rsa
- * when we wanted rsa-sha2-*). */
- const ssh_keyalg *sig_alg = ssh_key_alg(ca_key);
- if ((sig_alg == &ssh_rsa && !opts->permit_rsa_sha1) ||
- (sig_alg == &ssh_rsa_sha256 && !opts->permit_rsa_sha256) ||
- (sig_alg == &ssh_rsa_sha512 && !opts->permit_rsa_sha512)) {
- put_fmt(error, "Certificate signature uses '%s' signature type "
- "(forbidden by user configuration)", sig_alg->ssh_id);
- goto out;
- }
- opensshcert_signature_preimage(ck, BinarySink_UPCAST(preimage));
- if (!ssh_key_verify(ca_key, signature, ptrlen_from_strbuf(preimage))) {
- put_fmt(error, "Certificate's signature is invalid");
- goto out;
- }
- uint32_t expected_type = host ? SSH_CERT_TYPE_HOST : SSH_CERT_TYPE_USER;
- if (ck->type != expected_type) {
- put_fmt(error, "Certificate type is ");
- switch (ck->type) {
- case SSH_CERT_TYPE_HOST:
- put_fmt(error, "host");
- break;
- case SSH_CERT_TYPE_USER:
- put_fmt(error, "user");
- break;
- default:
- put_fmt(error, "unknown value %" PRIu32, ck->type);
- break;
- }
- put_fmt(error, "; expected %s", host ? "host" : "user");
- goto out;
- }
- /*
- * Check the time bounds on the certificate.
- */
- if (time < ck->valid_after) {
- put_fmt(error, "Certificate is not valid until ");
- opensshcert_time_to_iso8601(BinarySink_UPCAST(error),
- ck->valid_after);
- goto out;
- }
- if (time >= ck->valid_before) {
- put_fmt(error, "Certificate expired at ");
- opensshcert_time_to_iso8601(BinarySink_UPCAST(error),
- ck->valid_before);
- goto out;
- }
- /*
- * Check that this certificate is for the right thing.
- *
- * If valid_principals is a zero-length string then this is
- * specified to be a carte-blanche certificate valid for any
- * principal (at least, provided you trust the CA that issued it).
- */
- if (ck->valid_principals->len != 0) {
- BinarySource_BARE_INIT_PL(
- src, ptrlen_from_strbuf(ck->valid_principals));
- while (get_avail(src)) {
- ptrlen valid_principal = get_string(src);
- if (get_err(src)) {
- put_fmt(error, "Certificate's valid principals list is "
- "incorrectly formatted");
- goto out;
- }
- if (ptrlen_eq_ptrlen(valid_principal, principal))
- goto principal_ok;
- }
- /*
- * No valid principal matched. Now go through the list a
- * second time writing the cert contents into the error
- * message, so that the user can see at a glance what went
- * wrong.
- *
- * (If you've typed the wrong spelling of the host name, you
- * really need to see "This cert is for 'foo.example.com' and
- * I was trying to match it against 'foo'", rather than just
- * "Computer says no".)
- */
- put_fmt(error, "Certificate's %s list [",
- host ? "hostname" : "username");
- BinarySource_BARE_INIT_PL(
- src, ptrlen_from_strbuf(ck->valid_principals));
- const char *sep = "";
- while (get_avail(src)) {
- ptrlen valid_principal = get_string(src);
- put_fmt(error, "%s\"", sep);
- put_c_string_literal(error, valid_principal);
- put_fmt(error, "\"");
- sep = ", ";
- }
- put_fmt(error, "] does not contain expected %s \"",
- host ? "hostname" : "username");
- put_c_string_literal(error, principal);
- put_fmt(error, "\"");
- goto out;
- principal_ok:;
- }
- /*
- * Check for critical options.
- */
- {
- BinarySource_BARE_INIT_PL(
- src, ptrlen_from_strbuf(ck->critical_options));
- while (get_avail(src)) {
- ptrlen option = get_string(src);
- ptrlen data = get_string(src);
- if (get_err(src)) {
- put_fmt(error, "Certificate's critical options list is "
- "incorrectly formatted");
- goto out;
- }
- /*
- * If we ever do support any options, this will be where
- * we insert code to recognise and validate them.
- *
- * At present, we implement no critical options at all.
- * (For host certs, as of 2022-04-20, OpenSSH hasn't
- * defined any. For user certs, the only SSH server using
- * this is Uppity, which doesn't support key restrictions
- * in general.)
- */
- (void)data; /* no options supported => no use made of the data */
- /*
- * Report an unrecognised literal.
- */
- put_fmt(error, "Certificate specifies an unsupported critical "
- "option \"");
- put_c_string_literal(error, option);
- put_fmt(error, "\"");
- goto out;
- }
- }
- /* If we get here without failing any check, accept the certificate! */
- result = true;
- out:
- if (ca_key)
- ssh_key_free(ca_key);
- strbuf_free(preimage);
- return result;
- }
- static bool opensshcert_verify(ssh_key *key, ptrlen sig, ptrlen data)
- {
- /* This method is pure *signature* verification; checking the
- * certificate is done elsewhere. */
- opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
- return ssh_key_verify(ck->basekey, sig, data);
- }
- static void opensshcert_sign(ssh_key *key, ptrlen data, unsigned flags,
- BinarySink *bs)
- {
- opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
- ssh_key_sign(ck->basekey, data, flags, bs);
- }
|