File: x509ext.go

package info (click to toggle)
golang-github-google-go-attestation 0.5.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,260 kB
  • sloc: sh: 158; makefile: 22
file content (177 lines) | stat: -rw-r--r-- 5,074 bytes parent folder | download
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// Package x509ext provides functions for (un)marshalling X.509 extensions not
// supported by the crypto/x509 package.
package x509ext

import (
	"crypto/x509/pkix"
	"encoding/asn1"
	"errors"
	"fmt"

	"github.com/google/go-attestation/oid"
)

// RFC 4043
//
// https://tools.ietf.org/html/rfc4043
var (
	oidPermanentIdentifier = []int{1, 3, 6, 1, 5, 5, 7, 8, 3}
)

//	OtherName ::= SEQUENCE {
//	  type-id    OBJECT IDENTIFIER,
//	  value      [0] EXPLICIT ANY DEFINED BY type-id }
type otherName struct {
	TypeID asn1.ObjectIdentifier
	Value  asn1.RawValue
}

func marshalOtherName(typeID asn1.ObjectIdentifier, value interface{}) (asn1.RawValue, error) {
	valueBytes, err := asn1.MarshalWithParams(value, "explicit,tag:0")
	if err != nil {
		return asn1.RawValue{}, err
	}
	otherName := otherName{
		TypeID: typeID,
		Value:  asn1.RawValue{FullBytes: valueBytes},
	}
	bytes, err := asn1.MarshalWithParams(otherName, "tag:0")
	if err != nil {
		return asn1.RawValue{}, err
	}
	return asn1.RawValue{FullBytes: bytes}, nil
}

// PermanentIdentifier represents an ASN.1 encoded "permanent identifier" as
// defined by RFC4043.
//
//	PermanentIdentifier ::= SEQUENCE {
//	    identifierValue    UTF8String OPTIONAL,
//	    assigner           OBJECT IDENTIFIER OPTIONAL
//	   }
//
// https://datatracker.ietf.org/doc/html/rfc4043
type PermanentIdentifier struct {
	IdentifierValue string                `asn1:"utf8,optional"`
	Assigner        asn1.ObjectIdentifier `asn1:"optional"`
}

func parsePermanentIdentifier(der []byte) (PermanentIdentifier, error) {
	var permID PermanentIdentifier
	if _, err := asn1.UnmarshalWithParams(der, &permID, "explicit,tag:0"); err != nil {
		return PermanentIdentifier{}, err
	}
	return permID, nil
}

// SubjectAltName contains GeneralName variations not supported by the
// crypto/x509 package.
//
// https://datatracker.ietf.org/doc/html/rfc5280
type SubjectAltName struct {
	DirectoryNames       []pkix.Name
	PermanentIdentifiers []PermanentIdentifier
}

// ParseSubjectAltName parses a pkix.Extension into a SubjectAltName struct.
func ParseSubjectAltName(ext pkix.Extension) (*SubjectAltName, error) {
	var out SubjectAltName
	dirNames, otherNames, err := parseSubjectAltName(ext)
	if err != nil {
		return nil, fmt.Errorf("parseSubjectAltName: %v", err)
	}
	out.DirectoryNames = dirNames

	for _, otherName := range otherNames {
		if otherName.TypeID.Equal(oidPermanentIdentifier) {
			permID, err := parsePermanentIdentifier(otherName.Value.FullBytes)
			if err != nil {
				return nil, fmt.Errorf("parsePermanentIdentifier: %v", err)
			}
			out.PermanentIdentifiers = append(out.PermanentIdentifiers, permID)
		}
	}
	return &out, nil
}

// https://datatracker.ietf.org/doc/html/rfc5280#page-35
func parseSubjectAltName(ext pkix.Extension) (dirNames []pkix.Name, otherNames []otherName, err error) {
	err = forEachSAN(ext.Value, func(generalName asn1.RawValue) error {
		switch generalName.Tag {
		case 0: // otherName
			var otherName otherName
			if _, err := asn1.UnmarshalWithParams(generalName.FullBytes, &otherName, "tag:0"); err != nil {
				return fmt.Errorf("OtherName: asn1.UnmarshalWithParams: %v", err)
			}
			otherNames = append(otherNames, otherName)
		case 4: // directoryName
			var rdns pkix.RDNSequence
			if _, err := asn1.Unmarshal(generalName.Bytes, &rdns); err != nil {
				return fmt.Errorf("DirectoryName: asn1.Unmarshal: %v", err)
			}
			var dirName pkix.Name
			dirName.FillFromRDNSequence(&rdns)
			dirNames = append(dirNames, dirName)
		default:
			return fmt.Errorf("expected tag %d", generalName.Tag)
		}
		return nil
	})
	return
}

// Borrowed from the x509 package.
func forEachSAN(extension []byte, callback func(ext asn1.RawValue) error) error {
	var seq asn1.RawValue
	rest, err := asn1.Unmarshal(extension, &seq)
	if err != nil {
		return err
	} else if len(rest) != 0 {
		return errors.New("x509: trailing data after X.509 extension")
	}
	if !seq.IsCompound || seq.Tag != 16 || seq.Class != 0 {
		return asn1.StructuralError{Msg: "bad SAN sequence"}
	}

	rest = seq.Bytes
	for len(rest) > 0 {
		var v asn1.RawValue
		rest, err = asn1.Unmarshal(rest, &v)
		if err != nil {
			return err
		}

		if err := callback(v); err != nil {
			return err
		}
	}

	return nil
}

// MarshalSubjectAltName converts a SubjectAltName struct into a pkix.Extension.
func MarshalSubjectAltName(san *SubjectAltName) (pkix.Extension, error) {
	var generalNames []asn1.RawValue
	for _, permID := range san.PermanentIdentifiers {
		val, err := marshalOtherName(oidPermanentIdentifier, permID)
		if err != nil {
			return pkix.Extension{}, err
		}
		generalNames = append(generalNames, val)
	}
	for _, dirName := range san.DirectoryNames {
		bytes, err := asn1.MarshalWithParams(dirName.ToRDNSequence(), "explicit,tag:4")
		if err != nil {
			return pkix.Extension{}, err
		}
		generalNames = append(generalNames, asn1.RawValue{FullBytes: bytes})
	}
	val, err := asn1.Marshal(generalNames)
	if err != nil {
		return pkix.Extension{}, err
	}
	return pkix.Extension{
		Id:    oid.SubjectAltName,
		Value: val,
	}, nil
}