github.com/ztalab/ZACA@v0.0.1/pkg/pkiutil/san.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package pkiutil
    16  
    17  import (
    18  	"crypto/x509/pkix"
    19  	"encoding/asn1"
    20  	"fmt"
    21  	"net"
    22  	"strings"
    23  )
    24  
    25  // IdentityType represents type of an identity. This is used to properly encode
    26  // an identity into a SAN extension.
    27  type IdentityType int
    28  
    29  const (
    30  	// TypeDNS represents a DNS name.
    31  	TypeDNS IdentityType = iota
    32  	// TypeIP represents an IP address.
    33  	TypeIP
    34  	// TypeURI represents a universal resource identifier.
    35  	TypeURI
    36  )
    37  
    38  var (
    39  	// Mapping from the type of an identity to the OID tag value for the X.509
    40  	// SAN field (see https://tools.ietf.org/html/rfc5280#appendix-A.2)
    41  	//
    42  	// SubjectAltName ::= GeneralNames
    43  	//
    44  	// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
    45  	//
    46  	// GeneralName ::= CHOICE {
    47  	//      dNSName                         [2]     IA5String,
    48  	//      uniformResourceIdentifier       [6]     IA5String,
    49  	//      iPAddress                       [7]     OCTET STRING,
    50  	// }
    51  	oidTagMap = map[IdentityType]int{
    52  		TypeDNS: 2,
    53  		TypeURI: 6,
    54  		TypeIP:  7,
    55  	}
    56  
    57  	// A reversed map that maps from an OID tag to the corresponding identity
    58  	// type.
    59  	identityTypeMap = generateReversedMap(oidTagMap)
    60  
    61  	// The OID for the SAN extension (See
    62  	// http://www.alvestrand.no/objectid/2.5.29.17.html).
    63  	oidSubjectAlternativeName = asn1.ObjectIdentifier{2, 5, 29, 17}
    64  )
    65  
    66  // Identity is an object holding both the encoded identifier bytes as well as
    67  // the type of the identity.
    68  type Identity struct {
    69  	Type  IdentityType
    70  	Value []byte
    71  }
    72  
    73  // BuildSubjectAltNameExtension builds the SAN extension for the certificate.
    74  func BuildSubjectAltNameExtension(hosts string) (*pkix.Extension, error) {
    75  	ids := []Identity{}
    76  	for _, host := range strings.Split(hosts, ",") {
    77  		if ip := net.ParseIP(host); ip != nil {
    78  			// Use the 4-byte representation of the IP address when possible.
    79  			if eip := ip.To4(); eip != nil {
    80  				ip = eip
    81  			}
    82  			ids = append(ids, Identity{Type: TypeIP, Value: ip})
    83  		} else if strings.HasPrefix(host, "spiffe://") {
    84  			ids = append(ids, Identity{Type: TypeURI, Value: []byte(host)})
    85  		} else {
    86  			ids = append(ids, Identity{Type: TypeDNS, Value: []byte(host)})
    87  		}
    88  	}
    89  
    90  	san, err := BuildSANExtension(ids)
    91  	if err != nil {
    92  		return nil, fmt.Errorf("SAN extension building failure (%v)", err)
    93  	}
    94  
    95  	return san, nil
    96  }
    97  
    98  // BuildSANExtension builds a `pkix.Extension` of type "Subject
    99  // Alternative Name" based on the given identities.
   100  func BuildSANExtension(identites []Identity) (*pkix.Extension, error) {
   101  	rawValues := []asn1.RawValue{}
   102  	for _, i := range identites {
   103  		tag, ok := oidTagMap[i.Type]
   104  		if !ok {
   105  			return nil, fmt.Errorf("unsupported identity type: %v", i.Type)
   106  		}
   107  
   108  		rawValues = append(rawValues, asn1.RawValue{
   109  			Bytes: i.Value,
   110  			Class: asn1.ClassContextSpecific,
   111  			Tag:   tag,
   112  		})
   113  	}
   114  
   115  	bs, err := asn1.Marshal(rawValues)
   116  	if err != nil {
   117  		return nil, fmt.Errorf("failed to marshal the raw values for SAN field (err: %s)", err)
   118  	}
   119  
   120  	// SAN is Critical because the subject is empty. This is compliant with X.509 and SPIFFE standards.
   121  	return &pkix.Extension{Id: oidSubjectAlternativeName, Critical: true, Value: bs}, nil
   122  }
   123  
   124  // ExtractIDsFromSAN takes a SAN extension and extracts the identities.
   125  // The logic is mostly borrowed from
   126  // https://github.com/golang/go/blob/master/src/crypto/x509/x509.go, with the
   127  // addition of supporting extracting URIs.
   128  func ExtractIDsFromSAN(sanExt *pkix.Extension) ([]Identity, error) {
   129  	if !sanExt.Id.Equal(oidSubjectAlternativeName) {
   130  		return nil, fmt.Errorf("the input is not a SAN extension")
   131  	}
   132  
   133  	var sequence asn1.RawValue
   134  	if rest, err := asn1.Unmarshal(sanExt.Value, &sequence); err != nil {
   135  		return nil, err
   136  	} else if len(rest) != 0 {
   137  		return nil, fmt.Errorf("the SAN extension is incorrectly encoded")
   138  	}
   139  
   140  	// Check the rawValue is a sequence.
   141  	if !sequence.IsCompound || sequence.Tag != asn1.TagSequence || sequence.Class != asn1.ClassUniversal {
   142  		return nil, fmt.Errorf("the SAN extension is incorrectly encoded")
   143  	}
   144  
   145  	ids := []Identity{}
   146  	for bytes := sequence.Bytes; len(bytes) > 0; {
   147  		var rawValue asn1.RawValue
   148  		var err error
   149  
   150  		bytes, err = asn1.Unmarshal(bytes, &rawValue)
   151  		if err != nil {
   152  			return nil, err
   153  		}
   154  		ids = append(ids, Identity{Type: identityTypeMap[rawValue.Tag], Value: rawValue.Bytes})
   155  	}
   156  
   157  	return ids, nil
   158  }
   159  
   160  // ExtractSANExtension extracts the "Subject Alternative Name" externsion from
   161  // the given PKIX extension set.
   162  func ExtractSANExtension(exts []pkix.Extension) *pkix.Extension {
   163  	for _, ext := range exts {
   164  		if ext.Id.Equal(oidSubjectAlternativeName) {
   165  			// We don't need to examine other extensions anymore since a certificate
   166  			// must not include more than one instance of a particular extension. See
   167  			// https://tools.ietf.org/html/rfc5280#section-4.2.
   168  			return &ext
   169  		}
   170  	}
   171  	return nil
   172  }
   173  
   174  // ExtractIDs first finds the SAN extension from the given extension set, then
   175  // extract identities from the SAN extension.
   176  func ExtractIDs(exts []pkix.Extension) ([]string, error) {
   177  	sanExt := ExtractSANExtension(exts)
   178  	if sanExt == nil {
   179  		return nil, fmt.Errorf("the SAN extension does not exist")
   180  	}
   181  
   182  	idsWithType, err := ExtractIDsFromSAN(sanExt)
   183  	if err != nil {
   184  		return nil, fmt.Errorf("failed to extract identities from SAN extension (error %v)", err)
   185  	}
   186  
   187  	ids := []string{}
   188  	for _, id := range idsWithType {
   189  		ids = append(ids, string(id.Value))
   190  	}
   191  	return ids, nil
   192  }
   193  
   194  func generateReversedMap(m map[IdentityType]int) map[int]IdentityType {
   195  	reversed := make(map[int]IdentityType)
   196  	for key, value := range m {
   197  		reversed[value] = key
   198  	}
   199  	return reversed
   200  }