@@ -14,11 +14,12 @@ import (
1414 "strconv"
1515 "strings"
1616
17+ "github.com/prometheus/client_golang/prometheus"
18+ "gopkg.in/square/go-jose.v2"
19+
1720 "github.com/letsencrypt/boulder/core"
1821 berrors "github.com/letsencrypt/boulder/errors"
1922 "github.com/letsencrypt/boulder/probs"
20-
21- "gopkg.in/square/go-jose.v2"
2223)
2324
2425var sigAlgErr = errors .New ("no signature algorithms suitable for given key type" )
@@ -113,13 +114,13 @@ func (wfe *WebFrontEndImpl) enforceJWSAuthType(
113114 // Check the auth type for the provided JWS
114115 authType , prob := checkJWSAuthType (jws )
115116 if prob != nil {
116- wfe .stats .Inc ( "Errors.InvalidJWSAuth" , 1 )
117+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSAuthTypeInvalid" }). Inc ( )
117118 return prob
118119 }
119120 // If the auth type isn't the one expected return a sensible problem based on
120121 // what was expected
121122 if authType != expectedAuthType {
122- wfe .stats .Inc ( "Errors.WrongJWSAuthType" , 1 )
123+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSAuthTypeWrong" }). Inc ( )
123124 switch expectedAuthType {
124125 case embeddedKeyID :
125126 return probs .Malformed ("No Key ID in JWS header" )
@@ -136,20 +137,20 @@ func (wfe *WebFrontEndImpl) enforceJWSAuthType(
136137func (wfe * WebFrontEndImpl ) validPOSTRequest (request * http.Request ) * probs.ProblemDetails {
137138 // All POSTs should have an accompanying Content-Length header
138139 if _ , present := request .Header ["Content-Length" ]; ! present {
139- wfe .stats .Inc ( "HTTP.ClientErrors.LengthRequiredError" , 1 )
140+ wfe .stats .httpErrorCount . With (prometheus. Labels { "type" : "ContentLengthRequired" }). Inc ( )
140141 return probs .ContentLengthRequired ()
141142 }
142143
143144 // Per 6.4.1 "Replay-Nonce" clients should not send a Replay-Nonce header in
144145 // the HTTP request, it needs to be part of the signed JWS request body
145146 if _ , present := request .Header ["Replay-Nonce" ]; present {
146- wfe .stats .Inc ( "HTTP.ClientErrors.ReplayNonceOutsideJWSError" , 1 )
147+ wfe .stats .httpErrorCount . With (prometheus. Labels { "type" : "ReplayNonceOutsideJWS" }). Inc ( )
147148 return probs .Malformed ("HTTP requests should NOT contain Replay-Nonce header. Use JWS nonce field" )
148149 }
149150
150151 // All POSTs should have a non-nil body
151152 if request .Body == nil {
152- wfe .stats .Inc ( "HTTP.ClientErrors.NoPOSTBody" , 1 )
153+ wfe .stats .httpErrorCount . With (prometheus. Labels { "type" : "NoPOSTBody" }). Inc ( )
153154 return probs .Malformed ("No body on POST" )
154155 }
155156
@@ -168,10 +169,10 @@ func (wfe *WebFrontEndImpl) validNonce(jws *jose.JSONWebSignature, logEvent *req
168169 nonce := header .Nonce
169170 logEvent .RequestNonce = nonce
170171 if len (nonce ) == 0 {
171- wfe .stats .Inc ( "Errors.JWSMissingNonce" , 1 )
172+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSMissingNonce" }). Inc ( )
172173 return probs .BadNonce ("JWS has no anti-replay nonce" )
173174 } else if ! wfe .nonceService .Valid (nonce ) {
174- wfe .stats .Inc ( "Errors.JWSInvalidNonce" , 1 )
175+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSInvalidNonce" }). Inc ( )
175176 return probs .BadNonce (fmt .Sprintf ("JWS has an invalid anti-replay nonce: %q" , nonce ))
176177 }
177178 return nil
@@ -190,13 +191,13 @@ func (wfe *WebFrontEndImpl) validPOSTURL(
190191 extraHeaders := header .ExtraHeaders
191192 // Check that there is at least one Extra Header
192193 if len (extraHeaders ) == 0 {
193- wfe .stats .Inc ( "Errors.MissingURLinJWS" , 1 )
194+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSNoExtraHeaders" }). Inc ( )
194195 return probs .Malformed ("JWS header parameter 'url' required" )
195196 }
196197 // Try to read a 'url' Extra Header as a string
197198 headerURL , ok := extraHeaders [jose .HeaderKey ("url" )].(string )
198199 if ! ok || len (headerURL ) == 0 {
199- wfe .stats .Inc ( "Errors.MissingURLinJWS" , 1 )
200+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSMissingURL" }). Inc ( )
200201 return probs .Malformed ("JWS header parameter 'url' required" )
201202 }
202203 // Compute the URL we expect to be in the JWS based on the HTTP request
@@ -208,6 +209,7 @@ func (wfe *WebFrontEndImpl) validPOSTURL(
208209 // Check that the URL we expect is the one that was found in the signed JWS
209210 // header
210211 if expectedURL .String () != headerURL {
212+ wfe .stats .joseErrorCount .With (prometheus.Labels {"type" : "JWSMismatchedURL" }).Inc ()
211213 return probs .Malformed (fmt .Sprintf (
212214 "JWS header parameter 'url' incorrect. Expected %q got %q" ,
213215 expectedURL .String (), headerURL ))
@@ -218,25 +220,28 @@ func (wfe *WebFrontEndImpl) validPOSTURL(
218220// matchJWSURLs checks two JWS' URL headers are equal. This is used during key
219221// rollover to check that the inner JWS URL matches the outer JWS URL. If the
220222// JWS URLs do not match a problem is returned.
221- func matchJWSURLs (outer , inner * jose.JSONWebSignature ) * probs.ProblemDetails {
223+ func ( wfe * WebFrontEndImpl ) matchJWSURLs (outer , inner * jose.JSONWebSignature ) * probs.ProblemDetails {
222224 // Verify that the outer JWS has a non-empty URL header. This is strictly
223225 // defensive since the expectation is that endpoints using `matchJWSURLs`
224226 // have received at least one of their JWS from calling validPOSTForAccount(),
225227 // which checks the outer JWS has the expected URL header before processing
226228 // the inner JWS.
227229 outerURL , ok := outer .Signatures [0 ].Header .ExtraHeaders [jose .HeaderKey ("url" )].(string )
228230 if ! ok || len (outerURL ) == 0 {
231+ wfe .stats .joseErrorCount .With (prometheus.Labels {"type" : "KeyRolloverOuterJWSNoURL" }).Inc ()
229232 return probs .Malformed ("Outer JWS header parameter 'url' required" )
230233 }
231234
232235 // Verify the inner JWS has a non-empty URL header.
233236 innerURL , ok := inner .Signatures [0 ].Header .ExtraHeaders [jose .HeaderKey ("url" )].(string )
234237 if ! ok || len (innerURL ) == 0 {
238+ wfe .stats .joseErrorCount .With (prometheus.Labels {"type" : "KeyRolloverInnerJWSNoURL" }).Inc ()
235239 return probs .Malformed ("Inner JWS header parameter 'url' required" )
236240 }
237241
238242 // Verify that the outer URL matches the inner URL
239243 if outerURL != innerURL {
244+ wfe .stats .joseErrorCount .With (prometheus.Labels {"type" : "KeyRolloverMismatchedURLs" }).Inc ()
240245 return probs .Malformed (fmt .Sprintf (
241246 "Outer JWS 'url' value %q does not match inner JWS 'url' value %q" ,
242247 outerURL , innerURL ))
@@ -261,22 +266,22 @@ func (wfe *WebFrontEndImpl) parseJWS(body []byte) (*jose.JSONWebSignature, *prob
261266 Signatures []interface {}
262267 }
263268 if err := json .Unmarshal (body , & unprotected ); err != nil {
264- wfe .stats .Inc ( "Errors.JWSParseError" , 1 )
269+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSUnmarshalFailed" }). Inc ( )
265270 return nil , probs .Malformed ("Parse error reading JWS" )
266271 }
267272
268273 // ACME v2 never uses values from the unprotected JWS header. Reject JWS that
269274 // include unprotected headers.
270275 if unprotected .Header != nil {
271- wfe .stats .Inc ( "Errors.JWSUnprotectedHeaders" , 1 )
276+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSUnprotectedHeaders" }). Inc ( )
272277 return nil , probs .Malformed (
273278 "JWS \" header\" field not allowed. All headers must be in \" protected\" field" )
274279 }
275280
276281 // ACME v2 never uses the "signatures" array of JSON serialized JWS, just the
277282 // mandatory "signature" field. Reject JWS that include the "signatures" array.
278283 if len (unprotected .Signatures ) > 0 {
279- wfe .stats .Inc ( "Errors.JWSMultiSigField" , 1 )
284+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSMultiSig" }). Inc ( )
280285 return nil , probs .Malformed (
281286 "JWS \" signatures\" field not allowed. Only the \" signature\" field should contain a signature" )
282287 }
@@ -286,19 +291,19 @@ func (wfe *WebFrontEndImpl) parseJWS(body []byte) (*jose.JSONWebSignature, *prob
286291 bodyStr := string (body )
287292 parsedJWS , err := jose .ParseSigned (bodyStr )
288293 if err != nil {
289- wfe .stats .Inc ( "Errors.JWSParseError" , 1 )
294+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSParseError" }). Inc ( )
290295 return nil , probs .Malformed ("Parse error reading JWS" )
291296 }
292297 if len (parsedJWS .Signatures ) > 1 {
293- wfe .stats .Inc ( "Errors.TooManySignaturesInJWS" , 1 )
298+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSTooManySignatures" }). Inc ( )
294299 return nil , probs .Malformed ("Too many signatures in POST body" )
295300 }
296301 if len (parsedJWS .Signatures ) == 0 {
297- wfe .stats .Inc ( "Errors.NoSignaturesInJWS" , 1 )
302+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSNoSignatures" }). Inc ( )
298303 return nil , probs .Malformed ("POST JWS not signed" )
299304 }
300305 if len (parsedJWS .Signatures ) == 1 && len (parsedJWS .Signatures [0 ].Signature ) == 0 {
301- wfe .stats .Inc ( "Errors.EmptySignatureInJWS" , 1 )
306+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSEmptySignature" }). Inc ( )
302307 return nil , probs .Malformed ("POST JWS not signed" )
303308 }
304309
@@ -316,7 +321,7 @@ func (wfe *WebFrontEndImpl) parseJWSRequest(request *http.Request) (*jose.JSONWe
316321 // that the body is non-nil
317322 bodyBytes , err := ioutil .ReadAll (request .Body )
318323 if err != nil {
319- wfe .stats .Inc ( "Errors.UnableToReadRequestBody" , 1 )
324+ wfe .stats .httpErrorCount . With (prometheus. Labels { "type" : "UnableToReadReqBody" }). Inc ( )
320325 return nil , probs .ServerInternal ("unable to read request body" )
321326 }
322327
@@ -344,7 +349,7 @@ func (wfe *WebFrontEndImpl) extractJWK(jws *jose.JSONWebSignature) (*jose.JSONWe
344349
345350 // If the key isn't considered valid by go-jose return a problem immediately
346351 if ! key .Valid () {
347- wfe .stats .Inc ( "Errors.InvalidJWK" , 1 )
352+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWKInvalid" }). Inc ( )
348353 return nil , probs .Malformed ("Invalid JWK in JWS header" )
349354 }
350355
@@ -376,7 +381,7 @@ func (wfe *WebFrontEndImpl) lookupJWK(
376381 // GetRegistration RPC
377382 accountID , err := strconv .ParseInt (accountIDStr , 10 , 64 )
378383 if err != nil {
379- wfe .stats .Inc ( "Errors.InvalidKeyID" , 1 )
384+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSInvalidKeyID" }). Inc ( )
380385 return nil , nil , probs .Malformed (fmt .Sprintf ("Malformed account ID in KeyID header" ))
381386 }
382387
@@ -385,14 +390,14 @@ func (wfe *WebFrontEndImpl) lookupJWK(
385390 if err != nil {
386391 // If the account isn't found, return a suitable problem
387392 if berrors .Is (err , berrors .NotFound ) {
388- wfe .stats .Inc ( "Errors.KeyIDNotFound" , 1 )
393+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSKeyIDNotFound" }). Inc ( )
389394 return nil , nil , probs .AccountDoesNotExist (fmt .Sprintf (
390395 "Account %q not found" , accountURL ))
391396 }
392397
393398 // If there was an error and it isn't a "Not Found" error, return
394399 // a ServerInternal problem since this is unexpected.
395- wfe .stats .Inc ( "Errors.UnableToGetAccountByID" , 1 )
400+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSKeyIDLookupFailed" }). Inc ( )
396401 // Add an error to the log event with the internal error message
397402 logEvent .AddError (fmt .Sprintf ("Error calling SA.GetRegistration: %s" , err .Error ()))
398403 return nil , nil , probs .ServerInternal (fmt .Sprintf (
@@ -401,7 +406,7 @@ func (wfe *WebFrontEndImpl) lookupJWK(
401406
402407 // Verify the account is not deactivated
403408 if account .Status != core .StatusValid {
404- wfe .stats .Inc ( "Errors.AccountIsNotValid" , 1 )
409+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSKeyIDAccountInvalid" }). Inc ( )
405410 return nil , nil , probs .Unauthorized (
406411 fmt .Sprintf ("Account is not valid, has status %q" , account .Status ))
407412 }
@@ -426,6 +431,7 @@ func (wfe *WebFrontEndImpl) validJWSForKey(
426431
427432 // Check that the public key and JWS algorithms match expected
428433 if err := checkAlgorithm (jwk , jws ); err != nil {
434+ wfe .stats .joseErrorCount .With (prometheus.Labels {"type" : "JWSAlgorithmCheckFailed" }).Inc ()
429435 return nil , probs .Malformed (err .Error ())
430436 }
431437
@@ -437,7 +443,7 @@ func (wfe *WebFrontEndImpl) validJWSForKey(
437443 // the signature itself.
438444 payload , err := jws .Verify (jwk )
439445 if err != nil {
440- wfe .stats .Inc ( "Errors.JWSVerificationFailed" , 1 )
446+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSVerifyFailed" }). Inc ( )
441447 return nil , probs .Malformed ("JWS verification error" )
442448 }
443449 // Store the verified payload in the logEvent
@@ -460,7 +466,7 @@ func (wfe *WebFrontEndImpl) validJWSForKey(
460466 // early if it isn't valid JSON.
461467 var parsedBody struct {}
462468 if err := json .Unmarshal (payload , & parsedBody ); err != nil {
463- wfe .stats .Inc ( "Errors.UnparseableJWSPayload" , 1 )
469+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWSBodyUnmarshalFailed" }). Inc ( )
464470 return nil , probs .Malformed ("Request payload did not parse as JSON" )
465471 }
466472
@@ -529,7 +535,7 @@ func (wfe *WebFrontEndImpl) validSelfAuthenticatedPOST(
529535
530536 // If the key doesn't meet the GoodKey policy return a problem immediately
531537 if err := wfe .keyPolicy .GoodKey (pubKey .Key ); err != nil {
532- wfe .stats .Inc ( "Errors.JWKRejectedByGoodKey" , 1 )
538+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "JWKRejectedByGoodKey" }). Inc ( )
533539 return nil , nil , probs .Malformed (err .Error ())
534540 }
535541
@@ -576,7 +582,7 @@ func (wfe *WebFrontEndImpl) validKeyRollover(
576582
577583 // If the key doesn't meet the GoodKey policy return a problem immediately
578584 if err := wfe .keyPolicy .GoodKey (jwk .Key ); err != nil {
579- wfe .stats .Inc ( "Errors.InnerJWKRejectedByGoodKey" , 1 )
585+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "KeyRolloverJWKRejectedByGoodKey" }). Inc ( )
580586 return nil , probs .Malformed (err .Error ())
581587 }
582588
@@ -591,21 +597,22 @@ func (wfe *WebFrontEndImpl) validKeyRollover(
591597 // HTTP request to match the URL to)
592598 innerPayload , err := innerJWS .Verify (jwk )
593599 if err != nil {
594- wfe .stats .Inc ( "Errors.InnerJWSVerificationFailed" , 1 )
600+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "KeyRolloverJWSVerifyFailed" }). Inc ( )
595601 return nil , probs .Malformed ("Inner JWS does not verify with embedded JWK" )
596602 }
597603 // NOTE(@cpu): we do not stomp the requestEvent's payload here since that is set
598604 // from the outerJWS in validPOSTForAccount and contains the inner JWS and inner
599605 // payload already.
600606
601607 // Verify that the outer and inner JWS protected URL headers match
602- if matchJWSURLs (outerJWS , innerJWS ) != nil {
608+ if wfe . matchJWSURLs (outerJWS , innerJWS ) != nil {
603609 return nil , prob
604610 }
605611
606612 // Unmarshal the inner JWS' key roll over request
607613 var req rolloverRequest
608614 if json .Unmarshal (innerPayload , & req ) != nil {
615+ wfe .stats .joseErrorCount .With (prometheus.Labels {"type" : "KeyRolloverUnmarshalFailed" }).Inc ()
609616 return nil , probs .Malformed (
610617 "Inner JWS payload did not parse as JSON key rollover object" )
611618 }
@@ -614,7 +621,7 @@ func (wfe *WebFrontEndImpl) validKeyRollover(
614621 // JWS. So far we've only checked that the JWK embedded in the inner JWS valides
615622 // the JWS.
616623 if _ , err := innerJWS .Verify (req .NewKey ); err != nil {
617- wfe .stats .Inc ( "Errors.InnerJWSVerificationFailed" , 1 )
624+ wfe .stats .joseErrorCount . With (prometheus. Labels { "type" : "KeyRolloverJWSNewKeyVerifyFailed" }). Inc ( )
618625 return nil , probs .Malformed ("Inner JWS does not verify with specified new key" )
619626 }
620627
0 commit comments