1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddytls
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"math/big"
"github.com/caddyserver/certmagic"
)
// CustomCertSelectionPolicy represents a policy for selecting the certificate
// used to complete a handshake when there may be multiple options. All fields
// specified must match the candidate certificate for it to be chosen.
// This was needed to solve https://github.com/caddyserver/caddy/issues/2588.
type CustomCertSelectionPolicy struct {
// The certificate must have one of these serial numbers.
SerialNumber []bigInt `json:"serial_number,omitempty"`
// The certificate must have one of these organization names.
SubjectOrganization []string `json:"subject_organization,omitempty"`
// The certificate must use this public key algorithm.
PublicKeyAlgorithm PublicKeyAlgorithm `json:"public_key_algorithm,omitempty"`
// The certificate must have at least one of the tags in the list.
AnyTag []string `json:"any_tag,omitempty"`
// The certificate must have all of the tags in the list.
AllTags []string `json:"all_tags,omitempty"`
}
// SelectCertificate implements certmagic.CertificateSelector. It
// only chooses a certificate that at least meets the criteria in
// p. It then chooses the first non-expired certificate that is
// compatible with the client. If none are valid, it chooses the
// first viable candidate anyway.
func (p CustomCertSelectionPolicy) SelectCertificate(hello *tls.ClientHelloInfo, choices []certmagic.Certificate) (certmagic.Certificate, error) {
viable := make([]certmagic.Certificate, 0, len(choices))
nextChoice:
for _, cert := range choices {
if len(p.SerialNumber) > 0 {
var found bool
for _, sn := range p.SerialNumber {
if cert.Leaf.SerialNumber.Cmp(&sn.Int) == 0 {
found = true
break
}
}
if !found {
continue
}
}
if len(p.SubjectOrganization) > 0 {
var found bool
for _, subjOrg := range p.SubjectOrganization {
for _, org := range cert.Leaf.Subject.Organization {
if subjOrg == org {
found = true
break
}
}
}
if !found {
continue
}
}
if p.PublicKeyAlgorithm != PublicKeyAlgorithm(x509.UnknownPublicKeyAlgorithm) &&
PublicKeyAlgorithm(cert.Leaf.PublicKeyAlgorithm) != p.PublicKeyAlgorithm {
continue
}
if len(p.AnyTag) > 0 {
var found bool
for _, tag := range p.AnyTag {
if cert.HasTag(tag) {
found = true
break
}
}
if !found {
continue
}
}
if len(p.AllTags) > 0 {
for _, tag := range p.AllTags {
if !cert.HasTag(tag) {
continue nextChoice
}
}
}
// this certificate at least meets the policy's requirements,
// but we still have to check expiration and compatibility
viable = append(viable, cert)
}
if len(viable) == 0 {
return certmagic.Certificate{}, fmt.Errorf("no certificates matched custom selection policy")
}
return certmagic.DefaultCertificateSelector(hello, viable)
}
// bigInt is a big.Int type that interops with JSON encodings as a string.
type bigInt struct{ big.Int }
func (bi bigInt) MarshalJSON() ([]byte, error) {
return json.Marshal(bi.String())
}
func (bi *bigInt) UnmarshalJSON(p []byte) error {
if string(p) == "null" {
return nil
}
var stringRep string
err := json.Unmarshal(p, &stringRep)
if err != nil {
return err
}
_, ok := bi.SetString(stringRep, 10)
if !ok {
return fmt.Errorf("not a valid big integer: %s", p)
}
return nil
}
|