digest.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package digest
  2. import (
  3. "bytes"
  4. "crypto/sha256"
  5. "fmt"
  6. "hash"
  7. "io"
  8. "io/ioutil"
  9. "regexp"
  10. "strings"
  11. "github.com/docker/docker/pkg/tarsum"
  12. )
  13. const (
  14. // DigestTarSumV1EmptyTar is the digest for the empty tar file.
  15. DigestTarSumV1EmptyTar = "tarsum.v1+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
  16. // DigestSha256EmptyTar is the canonical sha256 digest of empty data
  17. DigestSha256EmptyTar = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
  18. )
  19. // Digest allows simple protection of hex formatted digest strings, prefixed
  20. // by their algorithm. Strings of type Digest have some guarantee of being in
  21. // the correct format and it provides quick access to the components of a
  22. // digest string.
  23. //
  24. // The following is an example of the contents of Digest types:
  25. //
  26. // sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc
  27. //
  28. // More important for this code base, this type is compatible with tarsum
  29. // digests. For example, the following would be a valid Digest:
  30. //
  31. // tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b
  32. //
  33. // This allows to abstract the digest behind this type and work only in those
  34. // terms.
  35. type Digest string
  36. // NewDigest returns a Digest from alg and a hash.Hash object.
  37. func NewDigest(alg string, h hash.Hash) Digest {
  38. return Digest(fmt.Sprintf("%s:%x", alg, h.Sum(nil)))
  39. }
  40. // NewDigestFromHex returns a Digest from alg and a the hex encoded digest.
  41. func NewDigestFromHex(alg, hex string) Digest {
  42. return Digest(fmt.Sprintf("%s:%s", alg, hex))
  43. }
  44. // DigestRegexp matches valid digest types.
  45. var DigestRegexp = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+`)
  46. // DigestRegexpAnchored matches valid digest types, anchored to the start and end of the match.
  47. var DigestRegexpAnchored = regexp.MustCompile(`^` + DigestRegexp.String() + `$`)
  48. var (
  49. // ErrDigestInvalidFormat returned when digest format invalid.
  50. ErrDigestInvalidFormat = fmt.Errorf("invalid checksum digest format")
  51. // ErrDigestUnsupported returned when the digest algorithm is unsupported by registry.
  52. ErrDigestUnsupported = fmt.Errorf("unsupported digest algorithm")
  53. )
  54. // ParseDigest parses s and returns the validated digest object. An error will
  55. // be returned if the format is invalid.
  56. func ParseDigest(s string) (Digest, error) {
  57. d := Digest(s)
  58. return d, d.Validate()
  59. }
  60. // FromReader returns the most valid digest for the underlying content.
  61. func FromReader(rd io.Reader) (Digest, error) {
  62. h := sha256.New()
  63. if _, err := io.Copy(h, rd); err != nil {
  64. return "", err
  65. }
  66. return NewDigest("sha256", h), nil
  67. }
  68. // FromTarArchive produces a tarsum digest from reader rd.
  69. func FromTarArchive(rd io.Reader) (Digest, error) {
  70. ts, err := tarsum.NewTarSum(rd, true, tarsum.Version1)
  71. if err != nil {
  72. return "", err
  73. }
  74. if _, err := io.Copy(ioutil.Discard, ts); err != nil {
  75. return "", err
  76. }
  77. d, err := ParseDigest(ts.Sum(nil))
  78. if err != nil {
  79. return "", err
  80. }
  81. return d, nil
  82. }
  83. // FromBytes digests the input and returns a Digest.
  84. func FromBytes(p []byte) (Digest, error) {
  85. return FromReader(bytes.NewReader(p))
  86. }
  87. // Validate checks that the contents of d is a valid digest, returning an
  88. // error if not.
  89. func (d Digest) Validate() error {
  90. s := string(d)
  91. // Common case will be tarsum
  92. _, err := ParseTarSum(s)
  93. if err == nil {
  94. return nil
  95. }
  96. // Continue on for general parser
  97. if !DigestRegexpAnchored.MatchString(s) {
  98. return ErrDigestInvalidFormat
  99. }
  100. i := strings.Index(s, ":")
  101. if i < 0 {
  102. return ErrDigestInvalidFormat
  103. }
  104. // case: "sha256:" with no hex.
  105. if i+1 == len(s) {
  106. return ErrDigestInvalidFormat
  107. }
  108. switch s[:i] {
  109. case "sha256", "sha384", "sha512":
  110. break
  111. default:
  112. return ErrDigestUnsupported
  113. }
  114. return nil
  115. }
  116. // Algorithm returns the algorithm portion of the digest. This will panic if
  117. // the underlying digest is not in a valid format.
  118. func (d Digest) Algorithm() string {
  119. return string(d[:d.sepIndex()])
  120. }
  121. // Hex returns the hex digest portion of the digest. This will panic if the
  122. // underlying digest is not in a valid format.
  123. func (d Digest) Hex() string {
  124. return string(d[d.sepIndex()+1:])
  125. }
  126. func (d Digest) String() string {
  127. return string(d)
  128. }
  129. func (d Digest) sepIndex() int {
  130. i := strings.Index(string(d), ":")
  131. if i < 0 {
  132. panic("could not find ':' in digest: " + d)
  133. }
  134. return i
  135. }