Skip to content

Commit

Permalink
Merge pull request #57 from smallstep/hw-module-name
Browse files Browse the repository at this point in the history
DirectoryName and HardwareModuleName SANs
  • Loading branch information
maraino authored Aug 22, 2022
2 parents f9d2043 + fd6d5e5 commit 58c55d2
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 67 deletions.
47 changes: 47 additions & 0 deletions x509util/certificate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"fmt"
"io"
"math/big"
Expand Down Expand Up @@ -606,3 +607,49 @@ func TestCreateCertificateTemplate(t *testing.T) {
})
}
}

func TestCreateCertificate_debug(t *testing.T) {
csr, _ := createCertificateRequest(t, "rocket", nil)
iss, issPriv := createIssuerCertificate(t, "issuer")

data := CreateTemplateData("rocket", nil)
data.Set(SANsKey, []SubjectAlternativeName{
{Type: DirectoryNameType, ASN1Value: []byte(`{"country":"US","organization":"ACME","commonName":"rocket"}`)},
})

tests := []struct {
name string
sans []SubjectAlternativeName
}{
{"directoryName", []SubjectAlternativeName{
{Type: DirectoryNameType, ASN1Value: []byte(`{"country":"US","organization":"ACME","commonName":"rocket"}`)},
}},
{"hardwareModuleName", []SubjectAlternativeName{
{Type: HardwareModuleNameType, ASN1Value: []byte(`{"type":"1.2.3.4","serialNumber":"MDEyMzQ1Njc4OQ=="}`)},
}},
{"permanentIdentifier", []SubjectAlternativeName{
{Type: PermanentIdentifierType, ASN1Value: []byte(`{"identifier":"0123456789","assigner":"1.2.3.4"}`)},
}},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data := CreateTemplateData("rocket", nil)
data.Set(SANsKey, tt.sans)

c, err := NewCertificate(csr, WithTemplate(DefaultLeafTemplate, data))
if err != nil {
t.Fatal(err)
}

template := c.GetCertificate()
cert, err := CreateCertificate(template, iss, csr.PublicKey, issPriv)
if err != nil {
t.Fatal(err)
}
t.Logf("\n%s", pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", Bytes: cert.Raw,
}))
})
}
}
125 changes: 108 additions & 17 deletions x509util/extensions.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package x509util

import (
"bytes"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
Expand Down Expand Up @@ -63,20 +64,21 @@ const (
IPType = "ip"
RegisteredIDType = "registeredID"
PermanentIdentifierType = "permanentIdentifier"
HardwareModuleNameType = "hardwareModuleName"
)

// These type ids are defined in RFC 5280 page 36
// nolint:deadcode // ignore
const (
nameTypeOtherName = 0
nameTypeEmail = 1
nameTypeDNS = 2
nameTypeX400 = 3
nameTypeDirectory = 4
nameTypeEDI = 5
nameTypeURI = 6
nameTypeIP = 7
nameTypeRegisteredID = 8
nameTypeOtherName = 0
nameTypeEmail = 1
nameTypeDNS = 2
nameTypeX400 = 3
nameTypeDirectoryName = 4
nameTypeEDI = 5
nameTypeURI = 6
nameTypeIP = 7
nameTypeRegisteredID = 8
)

// sanTypeSeparator is used to set the type of otherName SANs. The format string
Expand All @@ -87,6 +89,9 @@ const sanTypeSeparator = ":"
// RFC 4043 - https://datatracker.ietf.org/doc/html/rfc4043
var oidPermanentIdentifier = []int{1, 3, 6, 1, 5, 5, 7, 8, 3}

// RFC 4108 - https://www.rfc-editor.org/rfc/rfc4108
var oidHardwareModuleNameIdentifier = []int{1, 3, 6, 1, 5, 5, 7, 8, 4}

// RFC 5280 - https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6
//
// OtherName ::= SEQUENCE {
Expand All @@ -111,10 +116,50 @@ type otherName struct {
// assigner OBJECT IDENTIFIER OPTIONAL
// }
type PermanentIdentifier struct {
Identifier string `json:"identifier,omitempty"`
Assigner ObjectIdentifier `json:"assigner,omitempty"`
}

type asn1PermanentIdentifier struct {
IdentifierValue string `asn1:"utf8,optional"`
Assigner asn1.ObjectIdentifier `asn1:"optional"`
}

func (p *PermanentIdentifier) ans1Type() asn1PermanentIdentifier {
return asn1PermanentIdentifier{
IdentifierValue: p.Identifier,
Assigner: asn1.ObjectIdentifier(p.Assigner),
}
}

// HardwareModuleName is defined in RFC 4108 as an optional feature that by be
// used to identify a hardware module.
//
// The OID defined for this SAN is "1.3.6.1.5.5.7.8.4".
//
// See https://www.rfc-editor.org/rfc/rfc4108#section-5
//
// HardwareModuleName ::= SEQUENCE {
// hwType OBJECT IDENTIFIER,
// hwSerialNum OCTET STRING
// }
type HardwareModuleName struct {
Type ObjectIdentifier `json:"type"`
SerialNumber []byte `json:"serialNumber"`
}

type asn1HardwareModuleName struct {
Type asn1.ObjectIdentifier
SerialNumber []byte `asn1:"tag:4"`
}

func (h *HardwareModuleName) ans1Type() asn1HardwareModuleName {
return asn1HardwareModuleName{
Type: asn1.ObjectIdentifier(h.Type),
SerialNumber: h.SerialNumber,
}
}

// Extension is the JSON representation of a raw X.509 extensions.
type Extension struct {
ID ObjectIdentifier `json:"id"`
Expand Down Expand Up @@ -194,9 +239,13 @@ func (o *ObjectIdentifier) UnmarshalJSON(data []byte) error {
// SubjectAlternativeName represents a X.509 subject alternative name. Types
// supported are "dns", "email", "ip", "uri". A special type "auto" or "" can be
// used to try to guess the type of the value.
//
// ASN1Value can only be used for those types where the string value cannot
// contain enough information to encode the value.
type SubjectAlternativeName struct {
Type string `json:"type"`
Value string `json:"value"`
Type string `json:"type"`
Value string `json:"value"`
ASN1Value json.RawMessage `json:"asn1Value,omitempty"`
}

// Set sets the subject alternative name in the given x509.Certificate.
Expand Down Expand Up @@ -287,14 +336,56 @@ func (s SubjectAlternativeName) RawValue() (asn1.RawValue, error) {
}
return asn1.RawValue{FullBytes: rawBytes}, nil
case PermanentIdentifierType:
otherName, err := marshalOtherName(oidPermanentIdentifier, PermanentIdentifier{
IdentifierValue: s.Value,
})
var v PermanentIdentifier
switch {
case len(s.ASN1Value) != 0:
if err := json.Unmarshal(s.ASN1Value, &v); err != nil {
return zero, errors.Wrap(err, "error unmarshaling PermanentIdentifier SAN")
}
case s.Value != "":
v.Identifier = s.Value
default: // continue, both identifierValue and assigner are optional
}
otherName, err := marshalOtherName(oidPermanentIdentifier, v.ans1Type())
if err != nil {
return zero, errors.Wrap(err, "error marshaling PermanentIdentifier SAN")
}
return otherName, nil
case X400AddressType, DirectoryNameType, EDIPartyNameType:
case HardwareModuleNameType:
if len(s.ASN1Value) == 0 {
return zero, errors.New("error parsing HardwareModuleName SAN: empty asn1Value is not allowed")
}
var v HardwareModuleName
if err := json.Unmarshal(s.ASN1Value, &v); err != nil {
return zero, errors.Wrap(err, "error unmarshaling HardwareModuleName SAN")
}
otherName, err := marshalOtherName(oidHardwareModuleNameIdentifier, v.ans1Type())
if err != nil {
return zero, errors.Wrap(err, "error marshaling HardwareModuleName SAN")
}
return otherName, nil
case DirectoryNameType:
if len(s.ASN1Value) == 0 {
return zero, errors.New("error parsing DirectoryName SAN: empty asn1Value is not allowed")
}
var dn Name
if err := json.Unmarshal(s.ASN1Value, &dn); err != nil {
return zero, errors.Wrap(err, "error unmarshaling DirectoryName SAN")
}
rdn, err := asn1.Marshal(dn.goValue().ToRDNSequence())
if err != nil {
return zero, errors.Wrap(err, "error marshaling DirectoryName SAN")
}
if bytes.Equal(rdn, emptyASN1Subject) {
return zero, errors.New("error parsing DirectoryName SAN: empty or malformed ans1Value")
}
return asn1.RawValue{
Class: asn1.ClassContextSpecific,
Tag: nameTypeDirectoryName,
IsCompound: true,
Bytes: rdn,
}, nil
case X400AddressType, EDIPartyNameType:
return zero, fmt.Errorf("unimplemented SAN type %s", s.Type)
default:
// Assume otherName with a valid oid in type.
Expand Down Expand Up @@ -335,14 +426,14 @@ func marshalOtherName(oid asn1.ObjectIdentifier, value interface{}) (asn1.RawVal
if err != nil {
return asn1.RawValue{}, err
}
bytes, err := asn1.MarshalWithParams(otherName{
b, err := asn1.MarshalWithParams(otherName{
TypeID: oid,
Value: asn1.RawValue{FullBytes: valueBytes},
}, "tag:0")
if err != nil {
return asn1.RawValue{}, err
}
return asn1.RawValue{FullBytes: bytes}, nil
return asn1.RawValue{FullBytes: b}, nil
}

// marshalExplicitValue marshals the given value with given type and returns the
Expand Down
Loading

0 comments on commit 58c55d2

Please sign in to comment.