Skip to content

Commit

Permalink
did:x509: x5c claim is DER base64, not PEM
Browse files Browse the repository at this point in the history
  • Loading branch information
reinkrul committed Dec 10, 2024
1 parent 942c46e commit 029b1d5
Show file tree
Hide file tree
Showing 7 changed files with 402 additions and 524 deletions.
243 changes: 243 additions & 0 deletions test/pki/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,22 @@
package pki

import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
_ "embed"
"encoding/asn1"
"encoding/base64"
"github.com/lestrrat-go/jwx/v2/cert"
"github.com/nuts-foundation/nuts-node/test/io"
"math/big"
"net"
"os"
"path"
"testing"
"time"
)

// CertificateData contains the PEM-encoded test certificate and its key.
Expand Down Expand Up @@ -96,3 +105,237 @@ func writeToTemp(t *testing.T, fileName string, data []byte) string {
}
return filePath
}

func CertsToChain(certs []*x509.Certificate) *cert.Chain {
result := new(cert.Chain)
for _, c := range certs {
_ = result.Add([]byte(base64.StdEncoding.EncodeToString(c.Raw)))
}
return result
}

// BuildCertChain generates a certificate chain, including root, intermediate, and signing certificates.
func BuildCertChain(identifiers []string, subjectSerialNumber string) ([]*x509.Certificate, []*rsa.PrivateKey, error) {
rootKey, rootCert, err := BuildRootCert()
if err != nil {
return nil, nil, err
}
intermediateL1Key, intermediateL1Cert, err := buildIntermediateCert(rootCert, rootKey, "Intermediate CA Level 1")
if err != nil {
return nil, nil, err
}
intermediateL2Key, intermediateL2Cert, err := buildIntermediateCert(intermediateL1Cert, intermediateL1Key, "Intermediate CA Level 2")
if err != nil {
return nil, nil, err
}
if subjectSerialNumber == "" {
subjectSerialNumber = "32121323"
}
signingKey, signingCert, err := BuildSigningCert(identifiers, intermediateL2Cert, intermediateL2Key, subjectSerialNumber)
if err != nil {
return nil, nil, err
}
return []*x509.Certificate{
signingCert,
intermediateL2Cert,
intermediateL1Cert,
rootCert,
}, []*rsa.PrivateKey{
signingKey,
intermediateL2Key,
intermediateL1Key,
rootKey,
}, nil
}

func BuildSigningCert(identifiers []string, intermediateL2Cert *x509.Certificate, intermediateL2Key *rsa.PrivateKey, serialNumber string) (*rsa.PrivateKey, *x509.Certificate, error) {
signingKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
signingTmpl, err := SigningCertTemplate(nil, identifiers)
if err != nil {
return nil, nil, err
}
signingTmpl.Subject.SerialNumber = serialNumber
signingCert, err := CreateCert(signingTmpl, intermediateL2Cert, &signingKey.PublicKey, intermediateL2Key)
if err != nil {
return nil, nil, err
}
return signingKey, signingCert, err
}

func buildIntermediateCert(parentCert *x509.Certificate, parentKey *rsa.PrivateKey, subjectName string) (*rsa.PrivateKey, *x509.Certificate, error) {
intermediateL1Key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
intermediateL1Tmpl, err := CertTemplate(subjectName)
if err != nil {
return nil, nil, err
}
intermediateL1Cert, err := CreateCert(intermediateL1Tmpl, parentCert, &intermediateL1Key.PublicKey, parentKey)
if err != nil {
return nil, nil, err
}
return intermediateL1Key, intermediateL1Cert, nil
}

func BuildRootCert() (*rsa.PrivateKey, *x509.Certificate, error) {
rootKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
rootCertTmpl, err := CertTemplate("Root CA")
if err != nil {
return nil, nil, err
}
rootCert, err := CreateCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey)
if err != nil {
return nil, nil, err
}
return rootKey, rootCert, nil
}

// CertTemplate generates a template for a x509 certificate with a given serial number. If no serial number is provided, a random one is generated.
// The certificate is valid for one month and uses SHA256 with RSA for the signature algorithm.
func CertTemplate(subjectName string) (*x509.Certificate, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 8)
serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)
tmpl := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{Organization: []string{subjectName}},
SignatureAlgorithm: x509.SHA256WithRSA,
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 30), // valid for a month
BasicConstraintsValid: true,
}
tmpl.IsCA = true
tmpl.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature
tmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
return &tmpl, nil
}

// CreateCert generates a new x509 certificate using the provided template and parent certificates, public and private keys.
// It returns the generated certificate, its PEM-encoded version, and any error encountered during the process.
func CreateCert(template, parent *x509.Certificate, pub interface{}, parentPriv interface{}) (cert *x509.Certificate, err error) {
certDER, err := x509.CreateCertificate(rand.Reader, template, parent, pub, parentPriv)
if err != nil {
return nil, err
}
// parse the resulting certificate so we can use it again
cert, err = x509.ParseCertificate(certDER)
if err != nil {
return nil, err
}
return cert, err
}

// SigningCertTemplate creates a x509.Certificate template for a signing certificate with an optional serial number.
func SigningCertTemplate(serialNumber *big.Int, identifiers []string) (*x509.Certificate, error) {
// generate a random serial number (a real cert authority would have some logic behind this)
if serialNumber == nil {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 8)
serialNumber, _ = rand.Int(rand.Reader, serialNumberLimit)
}

tmpl := x509.Certificate{
SignatureAlgorithm: x509.SHA256WithRSA,
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"NUTS Foundation"},
CommonName: "www.example.com",
Country: []string{"NL"},
Locality: []string{"Amsterdam", "The Hague"},
OrganizationalUnit: []string{"The A-Team"},
StreetAddress: []string{"Amsterdamseweg 100"},
PostalCode: []string{"1011 NL"},
Province: []string{"Noord-Holland"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 30), // valid for a month
}
tmpl.KeyUsage = x509.KeyUsageDigitalSignature
tmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
// Either the ExtraExtensions SubjectAlternativeNameType is set, or the Subject Alternate Name values are set,
// both don't mix
if len(identifiers) > 0 {
err := addCertSan(&tmpl, identifiers, "testhost.example.com")
if err != nil {
return nil, err
}
} else {
tmpl.DNSNames = []string{"www.example.com", "example.com"}
tmpl.EmailAddresses = []string{"[email protected]", "[email protected]"}
tmpl.IPAddresses = []net.IP{net.ParseIP("192.1.2.3"), net.ParseIP("192.1.2.4")}
}
return &tmpl, nil
}

func addCertSan(tmpl *x509.Certificate, identifiers []string, altHostName string) error {
// OtherName represents a structure for other name in ASN.1
type OtherName struct {
TypeID asn1.ObjectIdentifier
Value asn1.RawValue `asn1:"tag:0,explicit"`
}
var (
// SubjectAlternativeNameType defines the OID for Subject Alternative Name
SubjectAlternativeNameType = asn1.ObjectIdentifier{2, 5, 29, 17}
// OtherNameType defines the OID for Other Name
OtherNameType = asn1.ObjectIdentifier{2, 5, 5, 5}
)

var list []asn1.RawValue
// Add the alternative host name first
value, err := ToRawValue(altHostName, "tag:2")
if err != nil {
return err
}
list = append(list, *value)

for _, identifier := range identifiers {
raw, err := ToRawValue(identifier, "ia5")
if err != nil {
return err
}
otherName := OtherName{
TypeID: OtherNameType,
Value: asn1.RawValue{
Class: 2,
Tag: 0,
IsCompound: true,
Bytes: raw.FullBytes,
},
}

raw, err = ToRawValue(otherName, "tag:0")
if err != nil {
return err
}
list = append(list, *raw)
}
marshal, err := asn1.Marshal(list)
if err != nil {
return err
}
tmpl.ExtraExtensions = append(tmpl.ExtraExtensions, pkix.Extension{
Id: SubjectAlternativeNameType,
Critical: false,
Value: marshal,
})
return nil
}

// toRawValue marshals an ASN.1 identifier with a given tag, then unmarshals it into a RawValue structure.
func ToRawValue(value any, tag string) (*asn1.RawValue, error) {
b, err := asn1.MarshalWithParams(value, tag)
if err != nil {
return nil, err
}
var val asn1.RawValue
_, err = asn1.Unmarshal(b, &val)
if err != nil {
return nil, err
}
return &val, nil
}
Loading

0 comments on commit 029b1d5

Please sign in to comment.