ocsp.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. /*
  2. Package ocsp exposes OCSP signing functionality, much like the signer
  3. package does for certificate signing. It also provies a basic OCSP
  4. responder stack for serving pre-signed OCSP responses.
  5. */
  6. package ocsp
  7. import (
  8. "bytes"
  9. "crypto"
  10. "crypto/x509"
  11. "crypto/x509/pkix"
  12. "io/ioutil"
  13. "strconv"
  14. "strings"
  15. "time"
  16. cferr "github.com/cloudflare/cfssl/errors"
  17. "github.com/cloudflare/cfssl/helpers"
  18. "github.com/cloudflare/cfssl/log"
  19. "golang.org/x/crypto/ocsp"
  20. )
  21. // revocationReasonCodes is a map between string reason codes
  22. // to integers as defined in RFC 5280
  23. var revocationReasonCodes = map[string]int{
  24. "unspecified": ocsp.Unspecified,
  25. "keycompromise": ocsp.KeyCompromise,
  26. "cacompromise": ocsp.CACompromise,
  27. "affiliationchanged": ocsp.AffiliationChanged,
  28. "superseded": ocsp.Superseded,
  29. "cessationofoperation": ocsp.CessationOfOperation,
  30. "certificatehold": ocsp.CertificateHold,
  31. "removefromcrl": ocsp.RemoveFromCRL,
  32. "privilegewithdrawn": ocsp.PrivilegeWithdrawn,
  33. "aacompromise": ocsp.AACompromise,
  34. }
  35. // StatusCode is a map between string statuses sent by cli/api
  36. // to ocsp int statuses
  37. var StatusCode = map[string]int{
  38. "good": ocsp.Good,
  39. "revoked": ocsp.Revoked,
  40. "unknown": ocsp.Unknown,
  41. }
  42. // SignRequest represents the desired contents of a
  43. // specific OCSP response.
  44. type SignRequest struct {
  45. Certificate *x509.Certificate
  46. Status string
  47. Reason int
  48. RevokedAt time.Time
  49. Extensions []pkix.Extension
  50. // IssuerHash is the hashing function used to hash the issuer subject and public key
  51. // in the OCSP response. Valid values are crypto.SHA1, crypto.SHA256, crypto.SHA384,
  52. // and crypto.SHA512. If zero, the default is crypto.SHA1.
  53. IssuerHash crypto.Hash
  54. // If provided ThisUpdate will override the default usage of time.Now().Truncate(time.Hour)
  55. ThisUpdate *time.Time
  56. // If provided NextUpdate will override the default usage of ThisUpdate.Add(signerInterval)
  57. NextUpdate *time.Time
  58. }
  59. // Signer represents a general signer of OCSP responses. It is
  60. // responsible for populating all fields in the OCSP response that
  61. // are not reflected in the SignRequest.
  62. type Signer interface {
  63. Sign(req SignRequest) ([]byte, error)
  64. }
  65. // StandardSigner is the default concrete type of OCSP signer.
  66. // It represents a single responder (represented by a key and certificate)
  67. // speaking for a single issuer (certificate). It is assumed that OCSP
  68. // responses are issued at a regular interval, which is used to compute
  69. // the nextUpdate value based on the current time.
  70. type StandardSigner struct {
  71. issuer *x509.Certificate
  72. responder *x509.Certificate
  73. key crypto.Signer
  74. interval time.Duration
  75. }
  76. // ReasonStringToCode tries to convert a reason string to an integer code
  77. func ReasonStringToCode(reason string) (reasonCode int, err error) {
  78. // default to 0
  79. if reason == "" {
  80. return 0, nil
  81. }
  82. reasonCode, present := revocationReasonCodes[strings.ToLower(reason)]
  83. if !present {
  84. reasonCode, err = strconv.Atoi(reason)
  85. if err != nil {
  86. return
  87. }
  88. if reasonCode >= ocsp.AACompromise || reasonCode <= ocsp.Unspecified {
  89. return 0, cferr.New(cferr.OCSPError, cferr.InvalidStatus)
  90. }
  91. }
  92. return
  93. }
  94. // NewSignerFromFile reads the issuer cert, the responder cert and the responder key
  95. // from PEM files, and takes an interval in seconds
  96. func NewSignerFromFile(issuerFile, responderFile, keyFile string, interval time.Duration) (Signer, error) {
  97. log.Debug("Loading issuer cert: ", issuerFile)
  98. issuerBytes, err := helpers.ReadBytes(issuerFile)
  99. if err != nil {
  100. return nil, err
  101. }
  102. log.Debug("Loading responder cert: ", responderFile)
  103. responderBytes, err := ioutil.ReadFile(responderFile)
  104. if err != nil {
  105. return nil, err
  106. }
  107. log.Debug("Loading responder key: ", keyFile)
  108. keyBytes, err := ioutil.ReadFile(keyFile)
  109. if err != nil {
  110. return nil, cferr.Wrap(cferr.CertificateError, cferr.ReadFailed, err)
  111. }
  112. issuerCert, err := helpers.ParseCertificatePEM(issuerBytes)
  113. if err != nil {
  114. return nil, err
  115. }
  116. responderCert, err := helpers.ParseCertificatePEM(responderBytes)
  117. if err != nil {
  118. return nil, err
  119. }
  120. key, err := helpers.ParsePrivateKeyPEM(keyBytes)
  121. if err != nil {
  122. log.Debugf("Malformed private key %v", err)
  123. return nil, err
  124. }
  125. return NewSigner(issuerCert, responderCert, key, interval)
  126. }
  127. // NewSigner simply constructs a new StandardSigner object from the inputs,
  128. // taking the interval in seconds
  129. func NewSigner(issuer, responder *x509.Certificate, key crypto.Signer, interval time.Duration) (Signer, error) {
  130. return &StandardSigner{
  131. issuer: issuer,
  132. responder: responder,
  133. key: key,
  134. interval: interval,
  135. }, nil
  136. }
  137. // Sign is used with an OCSP signer to request the issuance of
  138. // an OCSP response.
  139. func (s StandardSigner) Sign(req SignRequest) ([]byte, error) {
  140. if req.Certificate == nil {
  141. return nil, cferr.New(cferr.OCSPError, cferr.ReadFailed)
  142. }
  143. // Verify that req.Certificate is issued under s.issuer
  144. if bytes.Compare(req.Certificate.RawIssuer, s.issuer.RawSubject) != 0 {
  145. return nil, cferr.New(cferr.OCSPError, cferr.IssuerMismatch)
  146. }
  147. err := req.Certificate.CheckSignatureFrom(s.issuer)
  148. if err != nil {
  149. return nil, cferr.Wrap(cferr.OCSPError, cferr.VerifyFailed, err)
  150. }
  151. var thisUpdate, nextUpdate time.Time
  152. if req.ThisUpdate != nil {
  153. thisUpdate = *req.ThisUpdate
  154. } else {
  155. // Round thisUpdate times down to the nearest hour
  156. thisUpdate = time.Now().Truncate(time.Hour)
  157. }
  158. if req.NextUpdate != nil {
  159. nextUpdate = *req.NextUpdate
  160. } else {
  161. nextUpdate = thisUpdate.Add(s.interval)
  162. }
  163. status, ok := StatusCode[req.Status]
  164. if !ok {
  165. return nil, cferr.New(cferr.OCSPError, cferr.InvalidStatus)
  166. }
  167. // If the OCSP responder is the same as the issuer, there is no need to
  168. // include any certificate in the OCSP response, which decreases the byte size
  169. // of OCSP responses dramatically.
  170. certificate := s.responder
  171. if s.issuer == s.responder || bytes.Equal(s.issuer.Raw, s.responder.Raw) {
  172. certificate = nil
  173. }
  174. template := ocsp.Response{
  175. Status: status,
  176. SerialNumber: req.Certificate.SerialNumber,
  177. ThisUpdate: thisUpdate,
  178. NextUpdate: nextUpdate,
  179. Certificate: certificate,
  180. ExtraExtensions: req.Extensions,
  181. IssuerHash: req.IssuerHash,
  182. }
  183. if status == ocsp.Revoked {
  184. template.RevokedAt = req.RevokedAt
  185. template.RevocationReason = req.Reason
  186. }
  187. return ocsp.CreateResponse(s.issuer, s.responder, template, s.key)
  188. }