541 lines
16 KiB
Go
541 lines
16 KiB
Go
package va
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math/big"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/letsencrypt/boulder/bdns"
|
|
"github.com/letsencrypt/boulder/core"
|
|
"github.com/letsencrypt/boulder/identifier"
|
|
"github.com/letsencrypt/boulder/probs"
|
|
"github.com/letsencrypt/boulder/test"
|
|
)
|
|
|
|
func tlsalpnChallenge() core.Challenge {
|
|
return createChallenge(core.ChallengeTypeTLSALPN01)
|
|
}
|
|
|
|
func tlsCertTemplate(names []string) *x509.Certificate {
|
|
return &x509.Certificate{
|
|
SerialNumber: big.NewInt(1337),
|
|
Subject: pkix.Name{
|
|
Organization: []string{"tests"},
|
|
},
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().AddDate(0, 0, 1),
|
|
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
BasicConstraintsValid: true,
|
|
|
|
DNSNames: names,
|
|
}
|
|
}
|
|
|
|
func makeACert(names []string) *tls.Certificate {
|
|
template := tlsCertTemplate(names)
|
|
certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
|
|
return &tls.Certificate{
|
|
Certificate: [][]byte{certBytes},
|
|
PrivateKey: &TheKey,
|
|
}
|
|
}
|
|
|
|
// tlssniSrvWithNames is kept around for the use of TestValidateTLSALPN01UnawareSrv
|
|
func tlssniSrvWithNames(t *testing.T, chall core.Challenge, names ...string) *httptest.Server {
|
|
cert := makeACert(names)
|
|
tlsConfig := &tls.Config{
|
|
Certificates: []tls.Certificate{*cert},
|
|
ClientAuth: tls.NoClientCert,
|
|
GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
return cert, nil
|
|
},
|
|
NextProtos: []string{"http/1.1"},
|
|
}
|
|
|
|
hs := httptest.NewUnstartedServer(http.DefaultServeMux)
|
|
hs.TLS = tlsConfig
|
|
hs.StartTLS()
|
|
return hs
|
|
}
|
|
|
|
func tlsalpn01SrvWithCert(
|
|
t *testing.T,
|
|
chall core.Challenge,
|
|
oid asn1.ObjectIdentifier,
|
|
names []string,
|
|
cert *tls.Certificate,
|
|
acmeCert *tls.Certificate,
|
|
minTLSVersion uint16) *httptest.Server {
|
|
tlsConfig := &tls.Config{
|
|
Certificates: []tls.Certificate{},
|
|
ClientAuth: tls.NoClientCert,
|
|
GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
if clientHello.ServerName != names[0] {
|
|
return nil, nil
|
|
}
|
|
if len(clientHello.SupportedProtos) == 1 && clientHello.SupportedProtos[0] == ACMETLS1Protocol {
|
|
return acmeCert, nil
|
|
}
|
|
return cert, nil
|
|
},
|
|
NextProtos: []string{"http/1.1", ACMETLS1Protocol},
|
|
MinVersion: minTLSVersion,
|
|
}
|
|
|
|
hs := httptest.NewUnstartedServer(http.DefaultServeMux)
|
|
hs.TLS = tlsConfig
|
|
hs.Config.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){
|
|
ACMETLS1Protocol: func(_ *http.Server, conn *tls.Conn, _ http.Handler) {
|
|
_ = conn.Close()
|
|
},
|
|
}
|
|
hs.StartTLS()
|
|
return hs
|
|
}
|
|
|
|
func tlsalpn01Srv(
|
|
t *testing.T,
|
|
chall core.Challenge,
|
|
oid asn1.ObjectIdentifier,
|
|
minTLSVersion uint16,
|
|
names ...string) *httptest.Server {
|
|
template := tlsCertTemplate(names)
|
|
certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
|
|
cert := &tls.Certificate{
|
|
Certificate: [][]byte{certBytes},
|
|
PrivateKey: &TheKey,
|
|
}
|
|
|
|
shasum := sha256.Sum256([]byte(chall.ProvidedKeyAuthorization))
|
|
encHash, _ := asn1.Marshal(shasum[:])
|
|
acmeExtension := pkix.Extension{
|
|
Id: oid,
|
|
Critical: true,
|
|
Value: encHash,
|
|
}
|
|
template.ExtraExtensions = []pkix.Extension{acmeExtension}
|
|
certBytes, _ = x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
|
|
acmeCert := &tls.Certificate{
|
|
Certificate: [][]byte{certBytes},
|
|
PrivateKey: &TheKey,
|
|
}
|
|
|
|
return tlsalpn01SrvWithCert(t, chall, oid, names, cert, acmeCert, minTLSVersion)
|
|
}
|
|
|
|
func TestTLSALPN01FailIP(t *testing.T) {
|
|
chall := tlsalpnChallenge()
|
|
hs := tlsalpn01Srv(t, chall, IdPeAcmeIdentifier, 0, "localhost")
|
|
va, _ := setup(hs, 0, "", nil)
|
|
|
|
port := getPort(hs)
|
|
_, prob := va.validateTLSALPN01(ctx, identifier.ACMEIdentifier{
|
|
Type: identifier.IdentifierType("ip"),
|
|
Value: net.JoinHostPort("127.0.0.1", strconv.Itoa(port)),
|
|
}, chall)
|
|
if prob == nil {
|
|
t.Fatalf("IdentifierType IP shouldn't have worked.")
|
|
}
|
|
test.AssertEquals(t, prob.Type, probs.MalformedProblem)
|
|
}
|
|
|
|
func slowTLSSrv() *httptest.Server {
|
|
server := httptest.NewUnstartedServer(http.DefaultServeMux)
|
|
server.TLS = &tls.Config{
|
|
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
time.Sleep(100 * time.Millisecond)
|
|
return makeACert([]string{"nomatter"}), nil
|
|
},
|
|
}
|
|
server.StartTLS()
|
|
return server
|
|
}
|
|
|
|
func TestTLSALPNTimeoutAfterConnect(t *testing.T) {
|
|
chall := tlsalpnChallenge()
|
|
hs := slowTLSSrv()
|
|
va, _ := setup(hs, 0, "", nil)
|
|
|
|
timeout := 50 * time.Millisecond
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
defer cancel()
|
|
|
|
started := time.Now()
|
|
_, prob := va.validateTLSALPN01(ctx, dnsi("slow.server"), chall)
|
|
if prob == nil {
|
|
t.Fatalf("Validation should've failed")
|
|
}
|
|
// Check that the TLS connection doesn't return before a timeout, and times
|
|
// out after the expected time
|
|
took := time.Since(started)
|
|
// Check that the HTTP connection doesn't return too fast, and times
|
|
// out after the expected time
|
|
if took < timeout/2 {
|
|
t.Fatalf("TLSSNI returned before %s (%s) with %#v", timeout, took, prob)
|
|
}
|
|
if took > 2*timeout {
|
|
t.Fatalf("TLSSNI didn't timeout after %s (took %s to return %#v)", timeout,
|
|
took, prob)
|
|
}
|
|
if prob == nil {
|
|
t.Fatalf("Connection should've timed out")
|
|
}
|
|
test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
|
|
expected := "Timeout during read (your server may be slow or overloaded)"
|
|
if prob.Detail != expected {
|
|
t.Errorf("Wrong error detail. Expected %q, got %q", expected, prob.Detail)
|
|
}
|
|
}
|
|
|
|
func TestTLSALPN01DialTimeout(t *testing.T) {
|
|
chall := tlsalpnChallenge()
|
|
hs := slowTLSSrv()
|
|
va, _ := setup(hs, 0, "", nil)
|
|
va.dnsClient = dnsMockReturnsUnroutable{&bdns.MockClient{}}
|
|
started := time.Now()
|
|
|
|
timeout := 50 * time.Millisecond
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
defer cancel()
|
|
|
|
// The only method I've found so far to trigger a connect timeout is to
|
|
// connect to an unrouteable IP address. This usually generates a connection
|
|
// timeout, but will rarely return "Network unreachable" instead. If we get
|
|
// that, just retry until we get something other than "Network unreachable".
|
|
var prob *probs.ProblemDetails
|
|
for i := 0; i < 20; i++ {
|
|
_, prob = va.validateTLSALPN01(ctx, dnsi("unroutable.invalid"), chall)
|
|
if prob != nil && strings.Contains(prob.Detail, "Network unreachable") {
|
|
continue
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
if prob == nil {
|
|
t.Fatalf("Validation should've failed")
|
|
}
|
|
// Check that the TLS connection doesn't return before a timeout, and times
|
|
// out after the expected time
|
|
took := time.Since(started)
|
|
// Check that the HTTP connection doesn't return too fast, and times
|
|
// out after the expected time
|
|
if took < timeout/2 {
|
|
t.Fatalf("TLSSNI returned before %s (%s) with %#v", timeout, took, prob)
|
|
}
|
|
if took > 2*timeout {
|
|
t.Fatalf("TLSSNI didn't timeout after %s", timeout)
|
|
}
|
|
if prob == nil {
|
|
t.Fatalf("Connection should've timed out")
|
|
}
|
|
test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
|
|
expected := "Timeout during connect (likely firewall problem)"
|
|
if prob.Detail != expected {
|
|
t.Errorf("Wrong error detail. Expected %q, got %q", expected, prob.Detail)
|
|
}
|
|
}
|
|
|
|
func TestTLSALPN01Refused(t *testing.T) {
|
|
chall := tlsalpnChallenge()
|
|
hs := tlsalpn01Srv(t, chall, IdPeAcmeIdentifier, 0, "localhost")
|
|
va, _ := setup(hs, 0, "", nil)
|
|
// Take down validation server and check that validation fails.
|
|
hs.Close()
|
|
_, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
|
|
if prob == nil {
|
|
t.Fatalf("Server's down; expected refusal. Where did we connect?")
|
|
}
|
|
test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
|
|
expected := "Connection refused"
|
|
if prob.Detail != expected {
|
|
t.Errorf("Wrong error detail. Expected %q, got %q", expected, prob.Detail)
|
|
}
|
|
}
|
|
|
|
func TestTLSALPN01TalkingToHTTP(t *testing.T) {
|
|
chall := tlsalpnChallenge()
|
|
hs := tlsalpn01Srv(t, chall, IdPeAcmeIdentifier, 0, "localhost")
|
|
va, _ := setup(hs, 0, "", nil)
|
|
httpOnly := httpSrv(t, "")
|
|
va.tlsPort = getPort(httpOnly)
|
|
|
|
_, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
|
|
test.AssertError(t, prob, "TLS-SNI-01 validation passed when talking to a HTTP-only server")
|
|
expected := "Server only speaks HTTP, not TLS"
|
|
if !strings.HasSuffix(prob.Detail, expected) {
|
|
t.Errorf("Got wrong error detail. Expected %q, got %q", expected, prob.Detail)
|
|
}
|
|
}
|
|
|
|
func brokenTLSSrv() *httptest.Server {
|
|
server := httptest.NewUnstartedServer(http.DefaultServeMux)
|
|
server.TLS = &tls.Config{
|
|
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
return nil, fmt.Errorf("Failing on purpose")
|
|
},
|
|
}
|
|
server.StartTLS()
|
|
return server
|
|
}
|
|
|
|
func TestTLSError(t *testing.T) {
|
|
chall := tlsalpnChallenge()
|
|
hs := brokenTLSSrv()
|
|
|
|
va, _ := setup(hs, 0, "", nil)
|
|
|
|
_, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
|
|
if prob == nil {
|
|
t.Fatalf("TLS validation should have failed: What cert was used?")
|
|
}
|
|
if prob.Type != probs.TLSProblem {
|
|
t.Errorf("Wrong problem type: got %s, expected type %s",
|
|
prob, probs.TLSProblem)
|
|
}
|
|
}
|
|
|
|
func TestDNSError(t *testing.T) {
|
|
chall := tlsalpnChallenge()
|
|
hs := brokenTLSSrv()
|
|
|
|
va, _ := setup(hs, 0, "", nil)
|
|
|
|
_, prob := va.validateTLSALPN01(ctx, dnsi("always.invalid"), chall)
|
|
if prob == nil {
|
|
t.Fatalf("TLS validation should have failed: what IP was used?")
|
|
}
|
|
if prob.Type != probs.DNSProblem {
|
|
t.Errorf("Wrong problem type: got %s, expected type %s",
|
|
prob, probs.DNSProblem)
|
|
}
|
|
}
|
|
|
|
func TestCertNames(t *testing.T) {
|
|
// We duplicate names inside the SAN set
|
|
names := []string{
|
|
"hello.world", "goodbye.world",
|
|
"hello.world", "goodbye.world",
|
|
"bonjour.le.monde", "au.revoir.le.monde",
|
|
"bonjour.le.monde", "au.revoir.le.monde",
|
|
"f\xffoo", "f\xffoo",
|
|
}
|
|
// We expect only unique names, in sorted order and with any invalid utf-8
|
|
// replaced.
|
|
expected := []string{
|
|
"au.revoir.le.monde", "bonjour.le.monde",
|
|
"f\ufffdoo", "goodbye.world", "hello.world",
|
|
}
|
|
template := &x509.Certificate{
|
|
SerialNumber: big.NewInt(1337),
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().AddDate(0, 0, 1),
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
BasicConstraintsValid: true,
|
|
|
|
Subject: pkix.Name{
|
|
// We also duplicate a name from the SANs as the CN
|
|
CommonName: names[0],
|
|
},
|
|
DNSNames: names,
|
|
}
|
|
|
|
// Create the certificate, check that certNames provides the expected result
|
|
certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
|
|
cert, _ := x509.ParseCertificate(certBytes)
|
|
actual := certNames(cert)
|
|
test.AssertDeepEquals(t, actual, expected)
|
|
}
|
|
|
|
func TestTLSALPN01Success(t *testing.T) {
|
|
chall := tlsalpnChallenge()
|
|
hs := tlsalpn01Srv(t, chall, IdPeAcmeIdentifier, 0, "localhost")
|
|
|
|
va, _ := setup(hs, 0, "", nil)
|
|
|
|
_, prob := va.validateChallenge(ctx, dnsi("localhost"), chall)
|
|
if prob != nil {
|
|
t.Errorf("Validation failed: %v", prob)
|
|
}
|
|
test.AssertEquals(t, test.CountCounterVec("oid", IdPeAcmeIdentifier.String(), va.metrics.tlsALPNOIDCounter), 1)
|
|
|
|
hs.Close()
|
|
chall = tlsalpnChallenge()
|
|
hs = tlsalpn01Srv(t, chall, IdPeAcmeIdentifierV1Obsolete, 0, "localhost")
|
|
|
|
va, _ = setup(hs, 0, "", nil)
|
|
|
|
_, prob = va.validateChallenge(ctx, dnsi("localhost"), chall)
|
|
if prob != nil {
|
|
t.Errorf("Validation failed: %v", prob)
|
|
}
|
|
test.AssertEquals(t, test.CountCounterVec("oid", IdPeAcmeIdentifierV1Obsolete.String(), va.metrics.tlsALPNOIDCounter), 1)
|
|
}
|
|
|
|
func TestValidateTLSALPN01BadChallenge(t *testing.T) {
|
|
chall := tlsalpnChallenge()
|
|
chall2 := chall
|
|
setChallengeToken(&chall2, "bad token")
|
|
|
|
hs := tlsalpn01Srv(t, chall2, IdPeAcmeIdentifier, 0, "localhost")
|
|
va, _ := setup(hs, 0, "", nil)
|
|
|
|
_, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
|
|
|
|
if prob == nil {
|
|
t.Fatalf("TLS ALPN validation should have failed.")
|
|
}
|
|
test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
|
|
|
|
expectedDigest := sha256.Sum256([]byte(chall.ProvidedKeyAuthorization))
|
|
badDigest := sha256.Sum256([]byte(chall2.ProvidedKeyAuthorization))
|
|
|
|
test.AssertEquals(t, prob.Detail, fmt.Sprintf(
|
|
"Incorrect validation certificate for %s challenge. "+
|
|
"Expected acmeValidationV1 extension value %s for this challenge but got %s",
|
|
core.ChallengeTypeTLSALPN01,
|
|
hex.EncodeToString(expectedDigest[:]),
|
|
hex.EncodeToString(badDigest[:])))
|
|
}
|
|
|
|
func TestValidateTLSALPN01BrokenSrv(t *testing.T) {
|
|
chall := tlsalpnChallenge()
|
|
hs := brokenTLSSrv()
|
|
|
|
va, _ := setup(hs, 0, "", nil)
|
|
|
|
_, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
|
|
if prob == nil {
|
|
t.Fatalf("TLS ALPN validation should have failed.")
|
|
}
|
|
test.AssertEquals(t, prob.Type, probs.TLSProblem)
|
|
}
|
|
|
|
func TestValidateTLSALPN01UnawareSrv(t *testing.T) {
|
|
chall := tlsalpnChallenge()
|
|
hs := tlssniSrvWithNames(t, chall, "localhost")
|
|
|
|
va, _ := setup(hs, 0, "", nil)
|
|
|
|
_, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
|
|
if prob == nil {
|
|
t.Fatalf("TLS ALPN validation should have failed.")
|
|
}
|
|
test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
|
|
}
|
|
|
|
// TestValidateTLSALPN01BadUTFSrv tests that validating TLS-ALPN-01 against
|
|
// a host that returns a certificate with a SAN/CN that contains invalid UTF-8
|
|
// will result in a problem with the invalid UTF-8 replaced.
|
|
func TestValidateTLSALPN01BadUTFSrv(t *testing.T) {
|
|
chall := tlsalpnChallenge()
|
|
hs := tlsalpn01Srv(t, chall, IdPeAcmeIdentifier, 0, "localhost", "\xf0\x28\x8c\xbc")
|
|
port := getPort(hs)
|
|
va, _ := setup(hs, 0, "", nil)
|
|
|
|
_, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
|
|
if prob == nil {
|
|
t.Fatalf("TLS ALPN validation should have failed.")
|
|
}
|
|
test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
|
|
test.AssertEquals(t, prob.Detail, fmt.Sprintf(
|
|
"Incorrect validation certificate for tls-alpn-01 challenge. "+
|
|
"Requested localhost from 127.0.0.1:%d. Received 1 certificate(s), "+
|
|
`first certificate had names "localhost, %s"`,
|
|
port, "\ufffd(\ufffd\ufffd"))
|
|
}
|
|
|
|
// TestValidateTLSALPN01MalformedExtnValue tests that validating TLS-ALPN-01
|
|
// against a host that returns a certificate that contains an ASN.1 DER
|
|
// acmeValidation extension value that does not parse or is the wrong length
|
|
// will result in an Unauthorized problem
|
|
func TestValidateTLSALPN01MalformedExtnValue(t *testing.T) {
|
|
chall := tlsalpnChallenge()
|
|
|
|
names := []string{"localhost"}
|
|
template := tlsCertTemplate(names)
|
|
certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
|
|
cert := &tls.Certificate{
|
|
Certificate: [][]byte{certBytes},
|
|
PrivateKey: &TheKey,
|
|
}
|
|
|
|
wrongTypeDER, _ := asn1.Marshal("a string")
|
|
wrongLengthDER, _ := asn1.Marshal(make([]byte, 31))
|
|
badExtensions := []pkix.Extension{
|
|
{
|
|
Id: IdPeAcmeIdentifier,
|
|
Critical: true,
|
|
Value: wrongTypeDER,
|
|
},
|
|
{
|
|
Id: IdPeAcmeIdentifier,
|
|
Critical: true,
|
|
Value: wrongLengthDER,
|
|
},
|
|
}
|
|
|
|
malformedMsg := fmt.Sprintf("Incorrect validation certificate for %s challenge. "+
|
|
"Malformed acmeValidationV1 extension value", core.ChallengeTypeTLSALPN01)
|
|
|
|
for _, badExt := range badExtensions {
|
|
template.ExtraExtensions = []pkix.Extension{badExt}
|
|
certBytes, _ = x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
|
|
acmeCert := &tls.Certificate{
|
|
Certificate: [][]byte{certBytes},
|
|
PrivateKey: &TheKey,
|
|
}
|
|
|
|
hs := tlsalpn01SrvWithCert(t, chall, IdPeAcmeIdentifier, names, cert, acmeCert, 0)
|
|
va, _ := setup(hs, 0, "", nil)
|
|
|
|
_, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
|
|
hs.Close()
|
|
|
|
if prob == nil {
|
|
t.Errorf("TLS ALPN validation should have failed for acmeValidation extension %+v.",
|
|
badExt)
|
|
continue
|
|
}
|
|
test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
|
|
test.AssertEquals(t, prob.Detail, malformedMsg)
|
|
}
|
|
|
|
}
|
|
|
|
func TestTLSALPN01TLS13(t *testing.T) {
|
|
chall := tlsalpnChallenge()
|
|
// Create a server that uses tls.VersionTLS13 as the minimum supported version
|
|
hs := tlsalpn01Srv(t, chall, IdPeAcmeIdentifier, tls.VersionTLS13, "localhost")
|
|
defer hs.Close()
|
|
|
|
va, _ := setup(hs, 0, "", nil)
|
|
|
|
_, prob := va.validateChallenge(ctx, dnsi("localhost"), chall)
|
|
// Validation should not fail
|
|
if prob != nil {
|
|
t.Errorf("Validation failed: %v", prob)
|
|
}
|
|
// The correct TLS-ALPN-01 OID counter should have been incremented
|
|
test.AssertEquals(t, test.CountCounterVec(
|
|
"oid", IdPeAcmeIdentifier.String(), va.metrics.tlsALPNOIDCounter),
|
|
1)
|
|
}
|