blob: d9bc3d99aa43c9b1e30f9f898e1dffecb7888ec6 [file] [log] [blame]
// Copyright Istio 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 util
import (
"crypto/x509/pkix"
"encoding/asn1"
"fmt"
"net"
"strings"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/spiffe"
)
// IdentityType represents type of an identity. This is used to properly encode
// an identity into a SAN extension.
type IdentityType int
const (
// TypeDNS represents a DNS name.
TypeDNS IdentityType = iota
// TypeIP represents an IP address.
TypeIP
// TypeURI represents a universal resource identifier.
TypeURI
)
var (
// Mapping from the type of an identity to the OID tag value for the X.509
// SAN field (see https://tools.ietf.org/html/rfc5280#appendix-A.2)
//
// SubjectAltName ::= GeneralNames
//
// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
//
// GeneralName ::= CHOICE {
// dNSName [2] IA5String,
// uniformResourceIdentifier [6] IA5String,
// iPAddress [7] OCTET STRING,
// }
oidTagMap = map[IdentityType]int{
TypeDNS: 2,
TypeURI: 6,
TypeIP: 7,
}
// A reversed map that maps from an OID tag to the corresponding identity
// type.
identityTypeMap = generateReversedMap(oidTagMap)
// The OID for the SAN extension (See
// http://www.alvestrand.no/objectid/2.5.29.17.html).
oidSubjectAlternativeName = asn1.ObjectIdentifier{2, 5, 29, 17}
)
// Identity is an object holding both the encoded identifier bytes as well as
// the type of the identity.
type Identity struct {
Type IdentityType
Value []byte
}
// BuildSubjectAltNameExtension builds the SAN extension for the certificate.
func BuildSubjectAltNameExtension(hosts string) (*pkix.Extension, error) {
ids := []Identity{}
for _, host := range strings.Split(hosts, ",") {
if ip := net.ParseIP(host); ip != nil {
// Use the 4-byte representation of the IP address when possible.
if eip := ip.To4(); eip != nil {
ip = eip
}
ids = append(ids, Identity{Type: TypeIP, Value: ip})
} else if strings.HasPrefix(host, spiffe.URIPrefix) {
ids = append(ids, Identity{Type: TypeURI, Value: []byte(host)})
} else {
ids = append(ids, Identity{Type: TypeDNS, Value: []byte(host)})
}
}
san, err := BuildSANExtension(ids)
if err != nil {
return nil, fmt.Errorf("SAN extension building failure (%v)", err)
}
return san, nil
}
// BuildSANExtension builds a `pkix.Extension` of type "Subject
// Alternative Name" based on the given identities.
func BuildSANExtension(identites []Identity) (*pkix.Extension, error) {
rawValues := []asn1.RawValue{}
for _, i := range identites {
tag, ok := oidTagMap[i.Type]
if !ok {
return nil, fmt.Errorf("unsupported identity type: %v", i.Type)
}
rawValues = append(rawValues, asn1.RawValue{
Bytes: i.Value,
Class: asn1.ClassContextSpecific,
Tag: tag,
})
}
bs, err := asn1.Marshal(rawValues)
if err != nil {
return nil, fmt.Errorf("failed to marshal the raw values for SAN field (err: %s)", err)
}
// SAN is Critical because the subject is empty. This is compliant with X.509 and SPIFFE standards.
return &pkix.Extension{Id: oidSubjectAlternativeName, Critical: true, Value: bs}, nil
}
// ExtractIDsFromSAN takes a SAN extension and extracts the identities.
// The logic is mostly borrowed from
// https://github.com/golang/go/blob/master/src/crypto/x509/x509.go, with the
// addition of supporting extracting URIs.
func ExtractIDsFromSAN(sanExt *pkix.Extension) ([]Identity, error) {
if !sanExt.Id.Equal(oidSubjectAlternativeName) {
return nil, fmt.Errorf("the input is not a SAN extension")
}
var sequence asn1.RawValue
if rest, err := asn1.Unmarshal(sanExt.Value, &sequence); err != nil {
return nil, err
} else if len(rest) != 0 {
return nil, fmt.Errorf("the SAN extension is incorrectly encoded")
}
// Check the rawValue is a sequence.
if !sequence.IsCompound || sequence.Tag != asn1.TagSequence || sequence.Class != asn1.ClassUniversal {
return nil, fmt.Errorf("the SAN extension is incorrectly encoded")
}
ids := []Identity{}
for bytes := sequence.Bytes; len(bytes) > 0; {
var rawValue asn1.RawValue
var err error
bytes, err = asn1.Unmarshal(bytes, &rawValue)
if err != nil {
return nil, err
}
ids = append(ids, Identity{Type: identityTypeMap[rawValue.Tag], Value: rawValue.Bytes})
}
return ids, nil
}
// ExtractSANExtension extracts the "Subject Alternative Name" externsion from
// the given PKIX extension set.
func ExtractSANExtension(exts []pkix.Extension) *pkix.Extension {
for _, ext := range exts {
if ext.Id.Equal(oidSubjectAlternativeName) {
// We don't need to examine other extensions anymore since a certificate
// must not include more than one instance of a particular extension. See
// https://tools.ietf.org/html/rfc5280#section-4.2.
return &ext
}
}
return nil
}
// ExtractIDs first finds the SAN extension from the given extension set, then
// extract identities from the SAN extension.
func ExtractIDs(exts []pkix.Extension) ([]string, error) {
sanExt := ExtractSANExtension(exts)
if sanExt == nil {
return nil, fmt.Errorf("the SAN extension does not exist")
}
idsWithType, err := ExtractIDsFromSAN(sanExt)
if err != nil {
return nil, fmt.Errorf("failed to extract identities from SAN extension (error %v)", err)
}
ids := []string{}
for _, id := range idsWithType {
ids = append(ids, string(id.Value))
}
return ids, nil
}
func generateReversedMap(m map[IdentityType]int) map[int]IdentityType {
reversed := make(map[int]IdentityType)
for key, value := range m {
reversed[value] = key
}
return reversed
}