distsign.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package distsign implements signature and validation of arbitrary
  4. // distributable files.
  5. //
  6. // There are 3 parties in this exchange:
  7. // - builder, which creates files, signs them with signing keys and publishes
  8. // to server
  9. // - server, which distributes public signing keys, files and signatures
  10. // - client, which downloads files and signatures from server, and validates
  11. // the signatures
  12. //
  13. // There are 2 types of keys:
  14. // - signing keys, that sign individual distributable files on the builder
  15. // - root keys, that sign signing keys and are kept offline
  16. //
  17. // root keys -(sign)-> signing keys -(sign)-> files
  18. //
  19. // All keys are asymmetric Ed25519 key pairs.
  20. //
  21. // The server serves static files under some known prefix. The kinds of files are:
  22. // - distsign.pub - bundle of PEM-encoded public signing keys
  23. // - distsign.pub.sig - signature of distsign.pub using one of the root keys
  24. // - $file - any distributable file
  25. // - $file.sig - signature of $file using any of the signing keys
  26. //
  27. // The root public keys are baked into the client software at compile time.
  28. // These keys are long-lived and prove the validity of current signing keys
  29. // from distsign.pub. To rotate root keys, a new client release must be
  30. // published, they are not rotated dynamically. There are multiple root keys in
  31. // different locations specifically to allow this rotation without using the
  32. // discarded root key for any new signatures.
  33. //
  34. // The signing public keys are fetched by the client dynamically before every
  35. // download and can be rotated more readily, assuming that most deployed
  36. // clients trust the root keys used to issue fresh signing keys.
  37. package distsign
  38. import (
  39. "context"
  40. "crypto/ed25519"
  41. "crypto/rand"
  42. "encoding/binary"
  43. "encoding/pem"
  44. "errors"
  45. "fmt"
  46. "hash"
  47. "io"
  48. "log"
  49. "net/http"
  50. "net/url"
  51. "os"
  52. "time"
  53. "github.com/hdevalence/ed25519consensus"
  54. "golang.org/x/crypto/blake2s"
  55. "tailscale.com/feature"
  56. "tailscale.com/types/logger"
  57. "tailscale.com/util/httpm"
  58. "tailscale.com/util/must"
  59. )
  60. const (
  61. pemTypeRootPrivate = "ROOT PRIVATE KEY"
  62. pemTypeRootPublic = "ROOT PUBLIC KEY"
  63. pemTypeSigningPrivate = "SIGNING PRIVATE KEY"
  64. pemTypeSigningPublic = "SIGNING PUBLIC KEY"
  65. downloadSizeLimit = 1 << 29 // 512MB
  66. signingKeysSizeLimit = 1 << 20 // 1MB
  67. signatureSizeLimit = ed25519.SignatureSize
  68. )
  69. // RootKey is a root key used to sign signing keys.
  70. type RootKey struct {
  71. k ed25519.PrivateKey
  72. }
  73. // GenerateRootKey generates a new root key pair and encodes it as PEM.
  74. func GenerateRootKey() (priv, pub []byte, err error) {
  75. pub, priv, err = ed25519.GenerateKey(rand.Reader)
  76. if err != nil {
  77. return nil, nil, err
  78. }
  79. return pem.EncodeToMemory(&pem.Block{
  80. Type: pemTypeRootPrivate,
  81. Bytes: []byte(priv),
  82. }), pem.EncodeToMemory(&pem.Block{
  83. Type: pemTypeRootPublic,
  84. Bytes: []byte(pub),
  85. }), nil
  86. }
  87. // ParseRootKey parses the PEM-encoded private root key. The key must be in the
  88. // same format as returned by GenerateRootKey.
  89. func ParseRootKey(privKey []byte) (*RootKey, error) {
  90. k, err := parsePrivateKey(privKey, pemTypeRootPrivate)
  91. if err != nil {
  92. return nil, fmt.Errorf("failed to parse root key: %w", err)
  93. }
  94. return &RootKey{k: k}, nil
  95. }
  96. // SignSigningKeys signs the bundle of public signing keys. The bundle must be
  97. // a sequence of PEM blocks joined with newlines.
  98. func (r *RootKey) SignSigningKeys(pubBundle []byte) ([]byte, error) {
  99. if _, err := ParseSigningKeyBundle(pubBundle); err != nil {
  100. return nil, err
  101. }
  102. return ed25519.Sign(r.k, pubBundle), nil
  103. }
  104. // SigningKey is a signing key used to sign packages.
  105. type SigningKey struct {
  106. k ed25519.PrivateKey
  107. }
  108. // GenerateSigningKey generates a new signing key pair and encodes it as PEM.
  109. func GenerateSigningKey() (priv, pub []byte, err error) {
  110. pub, priv, err = ed25519.GenerateKey(rand.Reader)
  111. if err != nil {
  112. return nil, nil, err
  113. }
  114. return pem.EncodeToMemory(&pem.Block{
  115. Type: pemTypeSigningPrivate,
  116. Bytes: []byte(priv),
  117. }), pem.EncodeToMemory(&pem.Block{
  118. Type: pemTypeSigningPublic,
  119. Bytes: []byte(pub),
  120. }), nil
  121. }
  122. // ParseSigningKey parses the PEM-encoded private signing key. The key must be
  123. // in the same format as returned by GenerateSigningKey.
  124. func ParseSigningKey(privKey []byte) (*SigningKey, error) {
  125. k, err := parsePrivateKey(privKey, pemTypeSigningPrivate)
  126. if err != nil {
  127. return nil, fmt.Errorf("failed to parse root key: %w", err)
  128. }
  129. return &SigningKey{k: k}, nil
  130. }
  131. // SignPackageHash signs the hash and the length of a package. Use PackageHash
  132. // to compute the inputs.
  133. func (s *SigningKey) SignPackageHash(hash []byte, len int64) ([]byte, error) {
  134. if len <= 0 {
  135. return nil, fmt.Errorf("package length must be positive, got %d", len)
  136. }
  137. msg := binary.LittleEndian.AppendUint64(hash, uint64(len))
  138. return ed25519.Sign(s.k, msg), nil
  139. }
  140. // PackageHash is a hash.Hash that counts the number of bytes written. Use it
  141. // to get the hash and length inputs to SigningKey.SignPackageHash.
  142. type PackageHash struct {
  143. hash.Hash
  144. len int64
  145. }
  146. // NewPackageHash returns an initialized PackageHash using BLAKE2s.
  147. func NewPackageHash() *PackageHash {
  148. h, err := blake2s.New256(nil)
  149. if err != nil {
  150. // Should never happen with a nil key passed to blake2s.
  151. panic(err)
  152. }
  153. return &PackageHash{Hash: h}
  154. }
  155. func (ph *PackageHash) Write(b []byte) (int, error) {
  156. ph.len += int64(len(b))
  157. return ph.Hash.Write(b)
  158. }
  159. // Reset the PackageHash to its initial state.
  160. func (ph *PackageHash) Reset() {
  161. ph.len = 0
  162. ph.Hash.Reset()
  163. }
  164. // Len returns the total number of bytes written.
  165. func (ph *PackageHash) Len() int64 { return ph.len }
  166. // Client downloads and validates files from a distribution server.
  167. type Client struct {
  168. logf logger.Logf
  169. roots []ed25519.PublicKey
  170. pkgsAddr *url.URL
  171. }
  172. // NewClient returns a new client for distribution server located at pkgsAddr,
  173. // and uses embedded root keys from the roots/ subdirectory of this package.
  174. func NewClient(logf logger.Logf, pkgsAddr string) (*Client, error) {
  175. if logf == nil {
  176. logf = log.Printf
  177. }
  178. u, err := url.Parse(pkgsAddr)
  179. if err != nil {
  180. return nil, fmt.Errorf("invalid pkgsAddr %q: %w", pkgsAddr, err)
  181. }
  182. return &Client{logf: logf, roots: roots(), pkgsAddr: u}, nil
  183. }
  184. func (c *Client) url(path string) string {
  185. return c.pkgsAddr.JoinPath(path).String()
  186. }
  187. // Download fetches a file at path srcPath from pkgsAddr passed in NewClient.
  188. // The file is downloaded to dstPath and its signature is validated using the
  189. // embedded root keys. Download returns an error if anything goes wrong with
  190. // the actual file download or with signature validation.
  191. func (c *Client) Download(ctx context.Context, srcPath, dstPath string) error {
  192. // Always fetch a fresh signing key.
  193. sigPub, err := c.signingKeys()
  194. if err != nil {
  195. return err
  196. }
  197. srcURL := c.url(srcPath)
  198. sigURL := srcURL + ".sig"
  199. c.logf("Downloading %q", srcURL)
  200. dstPathUnverified := dstPath + ".unverified"
  201. hash, len, err := c.download(ctx, srcURL, dstPathUnverified, downloadSizeLimit)
  202. if err != nil {
  203. return err
  204. }
  205. c.logf("Downloading %q", sigURL)
  206. sig, err := fetch(sigURL, signatureSizeLimit)
  207. if err != nil {
  208. // Best-effort clean up of downloaded package.
  209. os.Remove(dstPathUnverified)
  210. return err
  211. }
  212. msg := binary.LittleEndian.AppendUint64(hash, uint64(len))
  213. if !VerifyAny(sigPub, msg, sig) {
  214. // Best-effort clean up of downloaded package.
  215. os.Remove(dstPathUnverified)
  216. return fmt.Errorf("signature %q for file %q does not validate with the current release signing key; either you are under attack, or attempting to download an old version of Tailscale which was signed with an older signing key", sigURL, srcURL)
  217. }
  218. c.logf("Signature OK")
  219. if err := os.Rename(dstPathUnverified, dstPath); err != nil {
  220. return fmt.Errorf("failed to move %q to %q after signature validation", dstPathUnverified, dstPath)
  221. }
  222. return nil
  223. }
  224. // ValidateLocalBinary fetches the latest signature associated with the binary
  225. // at srcURLPath and uses it to validate the file located on disk via
  226. // localFilePath. ValidateLocalBinary returns an error if anything goes wrong
  227. // with the signature download or with signature validation.
  228. func (c *Client) ValidateLocalBinary(srcURLPath, localFilePath string) error {
  229. // Always fetch a fresh signing key.
  230. sigPub, err := c.signingKeys()
  231. if err != nil {
  232. return err
  233. }
  234. srcURL := c.url(srcURLPath)
  235. sigURL := srcURL + ".sig"
  236. localFile, err := os.Open(localFilePath)
  237. if err != nil {
  238. return err
  239. }
  240. defer localFile.Close()
  241. h := NewPackageHash()
  242. _, err = io.Copy(h, localFile)
  243. if err != nil {
  244. return err
  245. }
  246. hash, hashLen := h.Sum(nil), h.Len()
  247. c.logf("Downloading %q", sigURL)
  248. sig, err := fetch(sigURL, signatureSizeLimit)
  249. if err != nil {
  250. return err
  251. }
  252. msg := binary.LittleEndian.AppendUint64(hash, uint64(hashLen))
  253. if !VerifyAny(sigPub, msg, sig) {
  254. return fmt.Errorf("signature %q for file %q does not validate with the current release signing key; either you are under attack, or attempting to download an old version of Tailscale which was signed with an older signing key", sigURL, localFilePath)
  255. }
  256. c.logf("Signature OK")
  257. return nil
  258. }
  259. // signingKeys fetches current signing keys from the server and validates them
  260. // against the roots. Should be called before validation of any downloaded file
  261. // to get the fresh keys.
  262. func (c *Client) signingKeys() ([]ed25519.PublicKey, error) {
  263. keyURL := c.url("distsign.pub")
  264. sigURL := keyURL + ".sig"
  265. raw, err := fetch(keyURL, signingKeysSizeLimit)
  266. if err != nil {
  267. return nil, err
  268. }
  269. sig, err := fetch(sigURL, signatureSizeLimit)
  270. if err != nil {
  271. return nil, err
  272. }
  273. if !VerifyAny(c.roots, raw, sig) {
  274. return nil, fmt.Errorf("signature %q for key %q does not validate with any known root key; either you are under attack, or running a very old version of Tailscale with outdated root keys", sigURL, keyURL)
  275. }
  276. keys, err := ParseSigningKeyBundle(raw)
  277. if err != nil {
  278. return nil, fmt.Errorf("cannot parse signing key bundle from %q: %w", keyURL, err)
  279. }
  280. return keys, nil
  281. }
  282. // fetch reads the response body from url into memory, up to limit bytes.
  283. func fetch(url string, limit int64) ([]byte, error) {
  284. resp, err := http.Get(url)
  285. if err != nil {
  286. return nil, err
  287. }
  288. defer resp.Body.Close()
  289. return io.ReadAll(io.LimitReader(resp.Body, limit))
  290. }
  291. // download writes the response body of url into a local file at dst, up to
  292. // limit bytes. On success, the returned value is a BLAKE2s hash of the file.
  293. func (c *Client) download(ctx context.Context, url, dst string, limit int64) ([]byte, int64, error) {
  294. tr := http.DefaultTransport.(*http.Transport).Clone()
  295. tr.Proxy = feature.HookProxyFromEnvironment.GetOrNil()
  296. defer tr.CloseIdleConnections()
  297. hc := &http.Client{
  298. Transport: tr,
  299. CheckRedirect: func(r *http.Request, via []*http.Request) error {
  300. c.logf("Download redirected to %q", r.URL)
  301. return nil
  302. },
  303. }
  304. quickCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
  305. defer cancel()
  306. headReq := must.Get(http.NewRequestWithContext(quickCtx, httpm.HEAD, url, nil))
  307. res, err := hc.Do(headReq)
  308. if err != nil {
  309. return nil, 0, err
  310. }
  311. if res.StatusCode != http.StatusOK {
  312. return nil, 0, fmt.Errorf("HEAD %q: %v", url, res.Status)
  313. }
  314. if res.ContentLength <= 0 {
  315. return nil, 0, fmt.Errorf("HEAD %q: unexpected Content-Length %v", url, res.ContentLength)
  316. }
  317. c.logf("Download size: %v", res.ContentLength)
  318. dlReq := must.Get(http.NewRequestWithContext(ctx, httpm.GET, url, nil))
  319. dlRes, err := hc.Do(dlReq)
  320. if err != nil {
  321. return nil, 0, err
  322. }
  323. defer dlRes.Body.Close()
  324. // TODO(bradfitz): resume from existing partial file on disk
  325. if dlRes.StatusCode != http.StatusOK {
  326. return nil, 0, fmt.Errorf("GET %q: %v", url, dlRes.Status)
  327. }
  328. of, err := os.Create(dst)
  329. if err != nil {
  330. return nil, 0, err
  331. }
  332. defer of.Close()
  333. pw := &progressWriter{total: res.ContentLength, logf: c.logf}
  334. h := NewPackageHash()
  335. n, err := io.Copy(io.MultiWriter(of, h, pw), io.LimitReader(dlRes.Body, limit))
  336. if err != nil {
  337. return nil, n, err
  338. }
  339. if n != res.ContentLength {
  340. return nil, n, fmt.Errorf("GET %q: downloaded %v, want %v", url, n, res.ContentLength)
  341. }
  342. if err := dlRes.Body.Close(); err != nil {
  343. return nil, n, err
  344. }
  345. if err := of.Close(); err != nil {
  346. return nil, n, err
  347. }
  348. pw.print()
  349. return h.Sum(nil), h.Len(), nil
  350. }
  351. type progressWriter struct {
  352. done int64
  353. total int64
  354. lastPrint time.Time
  355. logf logger.Logf
  356. }
  357. func (pw *progressWriter) Write(p []byte) (n int, err error) {
  358. pw.done += int64(len(p))
  359. if time.Since(pw.lastPrint) > 2*time.Second {
  360. pw.print()
  361. }
  362. return len(p), nil
  363. }
  364. func (pw *progressWriter) print() {
  365. pw.lastPrint = time.Now()
  366. pw.logf("Downloaded %v/%v (%.1f%%)", pw.done, pw.total, float64(pw.done)/float64(pw.total)*100)
  367. }
  368. func parsePrivateKey(data []byte, typeTag string) (ed25519.PrivateKey, error) {
  369. b, rest := pem.Decode(data)
  370. if b == nil {
  371. return nil, errors.New("failed to decode PEM data")
  372. }
  373. if len(rest) > 0 {
  374. return nil, errors.New("trailing PEM data")
  375. }
  376. if b.Type != typeTag {
  377. return nil, fmt.Errorf("PEM type is %q, want %q", b.Type, typeTag)
  378. }
  379. if len(b.Bytes) != ed25519.PrivateKeySize {
  380. return nil, errors.New("private key has incorrect length for an Ed25519 private key")
  381. }
  382. return ed25519.PrivateKey(b.Bytes), nil
  383. }
  384. // ParseSigningKeyBundle parses the bundle of PEM-encoded public signing keys.
  385. func ParseSigningKeyBundle(bundle []byte) ([]ed25519.PublicKey, error) {
  386. return parsePublicKeyBundle(bundle, pemTypeSigningPublic)
  387. }
  388. // ParseRootKeyBundle parses the bundle of PEM-encoded public root keys.
  389. func ParseRootKeyBundle(bundle []byte) ([]ed25519.PublicKey, error) {
  390. return parsePublicKeyBundle(bundle, pemTypeRootPublic)
  391. }
  392. func parsePublicKeyBundle(bundle []byte, typeTag string) ([]ed25519.PublicKey, error) {
  393. var keys []ed25519.PublicKey
  394. for len(bundle) > 0 {
  395. pub, rest, err := parsePublicKey(bundle, typeTag)
  396. if err != nil {
  397. return nil, err
  398. }
  399. keys = append(keys, pub)
  400. bundle = rest
  401. }
  402. if len(keys) == 0 {
  403. return nil, errors.New("no signing keys found in the bundle")
  404. }
  405. return keys, nil
  406. }
  407. func parseSinglePublicKey(data []byte, typeTag string) (ed25519.PublicKey, error) {
  408. pub, rest, err := parsePublicKey(data, typeTag)
  409. if err != nil {
  410. return nil, err
  411. }
  412. if len(rest) > 0 {
  413. return nil, errors.New("trailing PEM data")
  414. }
  415. return pub, err
  416. }
  417. func parsePublicKey(data []byte, typeTag string) (pub ed25519.PublicKey, rest []byte, retErr error) {
  418. b, rest := pem.Decode(data)
  419. if b == nil {
  420. return nil, nil, errors.New("failed to decode PEM data")
  421. }
  422. if b.Type != typeTag {
  423. return nil, nil, fmt.Errorf("PEM type is %q, want %q", b.Type, typeTag)
  424. }
  425. if len(b.Bytes) != ed25519.PublicKeySize {
  426. return nil, nil, errors.New("public key has incorrect length for an Ed25519 public key")
  427. }
  428. return ed25519.PublicKey(b.Bytes), rest, nil
  429. }
  430. // VerifyAny verifies whether sig is valid for msg using any of the keys.
  431. // VerifyAny will panic if any of the keys have the wrong size for Ed25519.
  432. func VerifyAny(keys []ed25519.PublicKey, msg, sig []byte) bool {
  433. for _, k := range keys {
  434. if ed25519consensus.Verify(k, msg, sig) {
  435. return true
  436. }
  437. }
  438. return false
  439. }