| 1 | // SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | |
| 3 | package gpgkey |
| 4 | |
| 5 | import ( |
| 6 | "errors" |
| 7 | "fmt" |
| 8 | ) |
| 9 | |
| 10 | // MinRSABits is the smallest accepted modulus length for OpenPGP RSA keys. |
| 11 | const MinRSABits = 2048 |
| 12 | |
| 13 | // MaxKeysPerUser bounds DB rows per user. Mirrors the SSH key cap. |
| 14 | const MaxKeysPerUser = 100 |
| 15 | |
| 16 | // Sentinel errors. The settings handler surfaces these verbatim as flash |
| 17 | // messages, so each one is a self-contained one-line description from the |
| 18 | // user's perspective. |
| 19 | var ( |
| 20 | // ErrPrivateKeyBlock fires when the uploaded armor is a private key |
| 21 | // block (BEGIN PGP PRIVATE KEY BLOCK). We never accept private |
| 22 | // material — the user almost certainly meant to upload the public |
| 23 | // half. |
| 24 | ErrPrivateKeyBlock = errors.New("gpgkey: that looks like a private key — please upload your public key (gpg --armor --export <id>)") |
| 25 | |
| 26 | // ErrSignatureBlock fires when the uploaded armor is a detached |
| 27 | // signature (BEGIN PGP SIGNATURE). |
| 28 | ErrSignatureBlock = errors.New("gpgkey: that looks like a signature, not a public key") |
| 29 | |
| 30 | // ErrUnparseable covers any other parse failure from the openpgp |
| 31 | // library. We deliberately don't surface library-internal error |
| 32 | // messages here; the user typically just needs to know it didn't |
| 33 | // parse and to try again. |
| 34 | ErrUnparseable = errors.New("gpgkey: could not parse key — please paste a public key armored block starting with -----BEGIN PGP PUBLIC KEY BLOCK-----") |
| 35 | |
| 36 | // ErrNoIdentities fires for entities with zero UIDs. OpenPGP |
| 37 | // theoretically allows this but no real-world keyring would produce |
| 38 | // one; reject so the rest of the pipeline can assume at least one |
| 39 | // uid. |
| 40 | ErrNoIdentities = errors.New("gpgkey: key has no user IDs") |
| 41 | |
| 42 | // ErrExpired fires for primary keys that are already expired at |
| 43 | // upload time. Past commits signed by the key remain verifiable |
| 44 | // (S52 territory); uploading a brand-new expired key has no |
| 45 | // consumer. |
| 46 | ErrExpired = errors.New("gpgkey: this key has expired") |
| 47 | |
| 48 | // ErrUnsupportedAlgo gates DSA and Elgamal-only entities (neither |
| 49 | // has a sensible signing path on modern git workflows). |
| 50 | ErrUnsupportedAlgo = errors.New("gpgkey: unsupported key algorithm (accepted: ed25519, ecdsa-nistp256/384/521, RSA ≥ 2048)") |
| 51 | |
| 52 | // ErrRSATooShort fires for RSA primary keys under MinRSABits. |
| 53 | ErrRSATooShort = fmt.Errorf("gpgkey: RSA keys must be at least %d bits", MinRSABits) |
| 54 | |
| 55 | // ErrMultipleEntities fires when the armor contains more than one |
| 56 | // entity (key + key, vs key + subkeys-which-are-fine). We accept |
| 57 | // exactly one primary per upload. |
| 58 | ErrMultipleEntities = errors.New("gpgkey: please upload one public key at a time") |
| 59 | |
| 60 | // ErrNameTooLong gates the optional user-given title. |
| 61 | ErrNameTooLong = errors.New("gpgkey: name may be at most 80 characters") |
| 62 | |
| 63 | // ErrNameControl gates control characters in the name field. |
| 64 | ErrNameControl = errors.New("gpgkey: name contains control characters") |
| 65 | ) |
| 66 |