ACMEv2 client library for Go.

nonce.go 2.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. package acmeapi
  2. import (
  3. "context"
  4. "errors"
  5. "sync"
  6. )
  7. // Stores a pool of nonces used to make replay-proof requests.
  8. type nonceSource struct {
  9. // If set, called when the nonce store is exhausted and a nonce is requested.
  10. // This function should add nonces to the nonceSource by calling AddNonce one
  11. // or more times. If this is not set, or if the function returns an error or
  12. // does not call AddNonce when called, an error is returned when attempting
  13. // to retrieve a nonce.
  14. GetNonceFunc func(ctx context.Context) error
  15. initOnce sync.Once
  16. pool map[string]struct{}
  17. poolMutex sync.Mutex
  18. }
  19. func (ns *nonceSource) init() {
  20. ns.initOnce.Do(func() {
  21. ns.pool = map[string]struct{}{}
  22. })
  23. }
  24. // Retrieves a new nonce. If no nonces remain in the pool, GetNonceFunc is used
  25. // if possible to retrieve a new one. This may result in network I/O, hence the
  26. // ctx parameter.
  27. func (ns *nonceSource) Nonce(ctx context.Context) (string, error) {
  28. ns.init()
  29. k := ns.tryPop()
  30. if k != "" {
  31. return k, nil
  32. }
  33. err := ns.obtainNonce(ctx)
  34. if err != nil {
  35. return "", err
  36. }
  37. k = ns.tryPop()
  38. if k != "" {
  39. return k, nil
  40. }
  41. return "", errors.New("failed to retrieve additional nonce")
  42. }
  43. func (ns *nonceSource) tryPop() string {
  44. ns.poolMutex.Lock()
  45. defer ns.poolMutex.Unlock()
  46. for k := range ns.pool {
  47. delete(ns.pool, k)
  48. return k
  49. }
  50. return ""
  51. }
  52. func (ns *nonceSource) obtainNonce(ctx context.Context) error {
  53. if ns.GetNonceFunc == nil {
  54. return errors.New("out of nonces - this should never happen")
  55. }
  56. return ns.GetNonceFunc(ctx)
  57. }
  58. // Add a nonce to the pool. This is a no-op if the nonce is already in the
  59. // pool.
  60. func (ns *nonceSource) AddNonce(nonce string) {
  61. ns.init()
  62. ns.poolMutex.Lock()
  63. defer ns.poolMutex.Unlock()
  64. ns.pool[nonce] = struct{}{}
  65. }
  66. // Returns a struct with a single method, Nonce() which can be called to obtain
  67. // a nonce. This adapts a nonceSource to an interface without a ctx parameter,
  68. // which some libraries may expect, while allowing calls made by that library
  69. // to still be made under a context.
  70. func (ns *nonceSource) WithContext(ctx context.Context) *nonceSourceWithCtx {
  71. return &nonceSourceWithCtx{ns, ctx}
  72. }
  73. type nonceSourceWithCtx struct {
  74. nonceSource *nonceSource
  75. ctx context.Context
  76. }
  77. // Obtain a nonce, using a context known by the object.
  78. func (nc *nonceSourceWithCtx) Nonce() (string, error) {
  79. return nc.nonceSource.Nonce(nc.ctx)
  80. }