diff --git a/did_x509/did_x509.go b/did_x509/did_x509.go index f4d1701..701a8f1 100644 --- a/did_x509/did_x509.go +++ b/did_x509/did_x509.go @@ -19,28 +19,6 @@ type X509Did struct { SanType x509_cert.SanTypeName } -// DidCreator is an interface for creating a DID (Decentralized Identifier) given a chain of x509 certificates. -// The CreateDid method takes a slice of x509.Certificate and returns a DID as a string and an error if any. -type DidCreator interface { - CreateDid(chain *[]x509.Certificate) (string, error) -} - -type DidParser interface { - ParseDid(did string) (*X509Did, error) -} - -// DefaultDidProcessor is responsible for creating Decentralized Identifiers (DIDs) based on certificate chain information. -type DefaultDidProcessor struct { -} - -// NewDidCreator initializes and returns a new instance of DefaultDidProcessor. -func NewDidCreator() *DefaultDidProcessor { - return &DefaultDidProcessor{} -} -func NewDidParser() *DefaultDidProcessor { - return &DefaultDidProcessor{} -} - // FormatDid constructs a decentralized identifier (DID) from a certificate chain and an optional policy. // It returns the formatted DID string or an error if the root certificate or hash calculation fails. func FormatDid(chain *[]x509.Certificate, policy string) (string, error) { @@ -64,7 +42,7 @@ func FormatDid(chain *[]x509.Certificate, policy string) (string, error) { // CreateDid generates a Decentralized Identifier (DID) from a given certificate chain. // It extracts the Unique Registration Address (URA) from the chain, creates a policy with it, and formats the DID. // Returns the generated DID or an error if any step fails. -func (d *DefaultDidProcessor) CreateDid(chain *[]x509.Certificate) (string, error) { +func CreateDid(chain *[]x509.Certificate) (string, error) { certificate, _, err := x509_cert.FindSigningCertificate(chain) if err != nil || certificate == nil { return "", err @@ -77,7 +55,7 @@ func (d *DefaultDidProcessor) CreateDid(chain *[]x509.Certificate) (string, erro formattedDid, err := FormatDid(chain, policy) return formattedDid, err } -func (d *DefaultDidProcessor) ParseDid(didString string) (*X509Did, error) { +func ParseDid(didString string) (*X509Did, error) { x509Did := X509Did{} didObj := did.MustParseDID(didString) if didObj.Method != "x509" { diff --git a/did_x509/did_x509_test.go b/did_x509/did_x509_test.go index 7e3747c..fd1f611 100644 --- a/did_x509/did_x509_test.go +++ b/did_x509/did_x509_test.go @@ -16,7 +16,7 @@ func TestDefaultDidCreator_CreateDid(t *testing.T) { type args struct { chain *[]x509.Certificate } - chain, _, rootCert, _, _, err := x509_cert.BuildCertChain("A BIG STRING") + chain, _, rootCert, _, _, err := x509_cert.BuildCertChain("A_BIG_STRING") if err != nil { t.Fatal(err) } @@ -54,14 +54,13 @@ func TestDefaultDidCreator_CreateDid(t *testing.T) { name: "Happy path", fields: fields{}, args: args{chain: chain}, - want: strings.Join([]string{"did", "x509", "0", alg, rootHashString, "", "san", "otherName", "A BIG STRING"}, ":"), + want: strings.Join([]string{"did", "x509", "0", alg, rootHashString, "", "san", "otherName", "A_BIG_STRING"}, ":"), errMsg: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - d := &DefaultDidProcessor{} - got, err := d.CreateDid(tt.args.chain) + got, err := CreateDid(tt.args.chain) wantErr := tt.errMsg != "" if (err != nil) != wantErr { t.Errorf("DefaultDidProcessor.CreateDid() error = %v, errMsg %v", err, tt.errMsg) @@ -78,3 +77,65 @@ func TestDefaultDidCreator_CreateDid(t *testing.T) { }) } } + +// TestDefaultDidCreator_ParseDid tests the ParseDid function of DefaultDidProcessor by providing different DID strings. +// It checks for correct X509Did parsing and appropriate error messages. +func TestDefaultDidCreator_ParseDid(t *testing.T) { + type fields struct { + } + type args struct { + didString string + } + tests := []struct { + name string + fields fields + args args + want *X509Did + errMsg string + }{ + { + name: "Invalid DID method", + fields: fields{}, + args: args{didString: "did:abc:0:sha512:hash::san:otherName:A_BIG_STRING"}, + want: nil, + errMsg: "invalid didString method", + }, + { + name: "Invalid DID format", + fields: fields{}, + args: args{didString: "did:x509:0:sha512::san:otherName:A_BIG_STRING"}, + want: nil, + errMsg: "invalid didString format, expected didString:x509:0:alg:hash::san:type:ura", + }, + { + name: "Happy path", + fields: fields{}, + args: args{didString: "did:x509:0:sha512:hash::san:otherName:A_BIG_STRING"}, + want: &X509Did{Version: "0", RootCertificateHashAlg: "sha512", RootCertificateHash: "hash", SanType: "otherName", Ura: "A_BIG_STRING"}, + errMsg: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseDid(tt.args.didString) + wantErr := tt.errMsg != "" + if (err != nil) != wantErr { + t.Errorf("DefaultDidProcessor.ParseDid() error = %v, expected error = %v", err, tt.errMsg) + return + } else if wantErr { + if err.Error() != tt.errMsg { + t.Errorf("DefaultDidProcessor.ParseDid() expected = \"%v\", got = \"%v\"", tt.errMsg, err.Error()) + } + } + + if tt.want != nil && got != nil && + (tt.want.Version != got.Version || + tt.want.RootCertificateHashAlg != got.RootCertificateHashAlg || + tt.want.RootCertificateHash != got.RootCertificateHash || + tt.want.SanType != got.SanType || + tt.want.Ura != got.Ura) { + t.Errorf("DefaultDidProcessor.ParseDid() = %v, want = %v", got, tt.want) + } + }) + } +} diff --git a/main.go b/main.go index 1fc1b39..dbc4feb 100644 --- a/main.go +++ b/main.go @@ -3,9 +3,7 @@ package main import ( "fmt" "github.com/alecthomas/kong" - "github.com/nuts-foundation/uzi-did-x509-issuer/did_x509" "github.com/nuts-foundation/uzi-did-x509-issuer/uzi_vc_issuer" - "github.com/nuts-foundation/uzi-did-x509-issuer/x509_cert" "os" ) @@ -41,8 +39,5 @@ func main() { } func issueVc(vc VC) (string, error) { - didCreator := did_x509.NewDidCreator() - chainParser := x509_cert.NewDefaultChainParser() - issuer := uzi_vc_issuer.NewUraVcBuilder(didCreator, chainParser) - return issuer.Issue(vc.CertificateFile, vc.SigningKey, vc.SubjectDID, vc.Test) + return uzi_vc_issuer.Issue(vc.CertificateFile, vc.SigningKey, vc.SubjectDID, vc.Test) } diff --git a/makefile b/makefile index ab5d584..ffe4cb1 100644 --- a/makefile +++ b/makefile @@ -7,9 +7,6 @@ install-tools: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.60.1 gen-mocks: - mockgen -destination=did_x509/did_x509_mock.go -package=did_x509 -source=did_x509/did_x509.go - mockgen -destination=pem/pem_reader_mock.go -package=pem -source=pem/pem_reader.go - mockgen -destination=x509_cert/x509_cert_mock.go -package=x509_cert -source=x509_cert/x509_cert.go lint: golangci-lint run -v diff --git a/pem/pem_reader.go b/pem/pem_reader.go index f6c2e31..03e0db7 100644 --- a/pem/pem_reader.go +++ b/pem/pem_reader.go @@ -5,22 +5,8 @@ import ( "os" ) -// PemReader defines the interface for parsing PEM-encoded files from a given path. -type PemReader interface { - ParseFileOrPath(path string, pemType string) (*[][]byte, error) -} - -// DefaultPemReader handles reading and parsing of PEM files or directories containing PEM files. -type DefaultPemReader struct { -} - -// NewPemReader creates and returns a new instance of DefaultPemReader. -func NewPemReader() *DefaultPemReader { - return &DefaultPemReader{} -} - // ParseFileOrPath processes a file or directory at the given path and extracts PEM blocks of the specified pemType. -func (p *DefaultPemReader) ParseFileOrPath(path string, pemType string) (*[][]byte, error) { +func ParseFileOrPath(path string, pemType string) (*[][]byte, error) { fileInfo, err := os.Stat(path) if err != nil { return nil, err diff --git a/pem/pem_reader_test.go b/pem/pem_reader_test.go index d62c26b..92b2ae6 100644 --- a/pem/pem_reader_test.go +++ b/pem/pem_reader_test.go @@ -21,15 +21,13 @@ func TestParseFileOrPath(t *testing.T) { pemType := "CERTIFICATE" t.Run("FileExistsAndIsNotDirectory", func(t *testing.T) { - pemReader := NewPemReader() - result, err := pemReader.ParseFileOrPath(tempFile.Name(), pemType) + result, err := ParseFileOrPath(tempFile.Name(), pemType) assert.NoError(t, err) assert.NotNil(t, result) }) t.Run("FileDoesNotExist", func(t *testing.T) { - pemReader := NewPemReader() - _, err := pemReader.ParseFileOrPath("nonexistent", pemType) + _, err := ParseFileOrPath("nonexistent", pemType) assert.Error(t, err) }) @@ -42,18 +40,15 @@ func TestParseFileOrPath(t *testing.T) { }(tempDir) t.Run("PathIsDirectory", func(t *testing.T) { - pemReader := NewPemReader() - _, err := pemReader.ParseFileOrPath(tempDir, pemType) + _, err := ParseFileOrPath(tempDir, pemType) assert.NoError(t, err) }) t.Run("PathDoesNotExist", func(t *testing.T) { - pemReader := NewPemReader() - _, err := pemReader.ParseFileOrPath("nonexistent/path", pemType) + _, err := ParseFileOrPath("nonexistent/path", pemType) assert.Error(t, err) }) t.Run("Happy flow single file", func(t *testing.T) { - pemReader := NewPemReader() file, err := os.CreateTemp(tempDir, "prefix") if err != nil { t.Fatal(err) @@ -78,7 +73,7 @@ func TestParseFileOrPath(t *testing.T) { t.Fail() } } - data, err := pemReader.ParseFileOrPath(file.Name(), pemType) + data, err := ParseFileOrPath(file.Name(), pemType) assert.NoError(t, err) for i := 0; i < len(*data); i++ { bytes := (*data)[i] @@ -91,7 +86,6 @@ func TestParseFileOrPath(t *testing.T) { }) t.Run("Happy flow directory", func(t *testing.T) { - pemReader := NewPemReader() certs, chainPem, _, _, _, err := x509_cert.BuildCertChain("A BIG STRING") assert.NoError(t, err) tempDir, _ := os.MkdirTemp("", "example") @@ -113,7 +107,7 @@ func TestParseFileOrPath(t *testing.T) { t.Fail() } } - data, err := pemReader.ParseFileOrPath(tempDir, pemType) + data, err := ParseFileOrPath(tempDir, pemType) assert.NoError(t, err) dataMap := make(map[string][]byte) for i := 0; i < len(*data); i++ { diff --git a/uzi_vc_issuer/ura_issuer.go b/uzi_vc_issuer/ura_issuer.go index 81c91dd..87cf79d 100644 --- a/uzi_vc_issuer/ura_issuer.go +++ b/uzi_vc_issuer/ura_issuer.go @@ -26,34 +26,15 @@ import ( ) import "github.com/nuts-foundation/go-did/vc" -type UraIssuer interface { - - // Issue generates a digital certificate from the given certificate file and signing key file for the subject. - Issue(certificateFile string, signingKeyFile string, subjectDID string, subjectName string) (string, error) -} - var RegexOtherNameValue = regexp.MustCompile(`2\.16\.528\.1\.1007.\d+\.\d+-\d+-\d+-S-(\d+)-00\.000-\d+`) -// DefaultUraIssuer is responsible for building URA (UZI-register abonneenummer) Verifiable Credentials. -// It utilizes a DidCreator to generate Decentralized Identifiers (DIDs) given a chain of x509 certificates. -type DefaultUraIssuer struct { - didCreator did_x509.DidCreator - chainParser x509_cert.ChainParser -} - -// NewUraVcBuilder initializes and returns a new instance of DefaultUraIssuer with the provided DidCreator. -func NewUraVcBuilder(didCreator did_x509.DidCreator, chainParser x509_cert.ChainParser) *DefaultUraIssuer { - return &DefaultUraIssuer{didCreator, chainParser} -} - // Issue generates a URA Verifiable Credential using provided certificate, signing key, subject DID, and subject name. -func (u DefaultUraIssuer) Issue(certificateFile string, signingKeyFile string, subjectDID string, test bool) (string, error) { - reader := pem2.NewPemReader() - certificate, err := reader.ParseFileOrPath(certificateFile, "CERTIFICATE") +func Issue(certificateFile string, signingKeyFile string, subjectDID string, test bool) (string, error) { + certificate, err := pem2.ParseFileOrPath(certificateFile, "CERTIFICATE") if err != nil { return "", err } - _certificates, err := u.chainParser.ParseCertificates(certificate) + _certificates, err := x509_cert.ParseCertificates(certificate) if err != nil { return "", err } @@ -69,7 +50,7 @@ func (u DefaultUraIssuer) Issue(certificateFile string, signingKeyFile string, s _chain := append(*chain, *certificate...) chain = &_chain - signingKeys, err := reader.ParseFileOrPath(signingKeyFile, "PRIVATE KEY") + signingKeys, err := pem2.ParseFileOrPath(signingKeyFile, "PRIVATE KEY") if err != nil { return "", err } @@ -85,17 +66,17 @@ func (u DefaultUraIssuer) Issue(certificateFile string, signingKeyFile string, s err := fmt.Errorf("no signing keys found") return "", err } - privateKey, err := u.chainParser.ParsePrivateKey(signingKey) + privateKey, err := x509_cert.ParsePrivateKey(signingKey) if err != nil { return "", err } - certChain, err := u.chainParser.ParseCertificates(chain) + certChain, err := x509_cert.ParseCertificates(chain) if err != nil { return "", err } - credential, err := u.BuildUraVerifiableCredential(certChain, privateKey, subjectDID) + credential, err := BuildUraVerifiableCredential(certChain, privateKey, subjectDID) if err != nil { return "", err } @@ -103,7 +84,7 @@ func (u DefaultUraIssuer) Issue(certificateFile string, signingKeyFile string, s if err != nil { return "", err } - validator := uzi_vc_validator.NewUraValidator(did_x509.NewDidParser(), test) + validator := uzi_vc_validator.NewUraValidator(test) jwtString := string(marshal) jwtString = jwtString[1:] // Chop start jwtString = jwtString[:len(jwtString)-1] // Chop end @@ -115,7 +96,7 @@ func (u DefaultUraIssuer) Issue(certificateFile string, signingKeyFile string, s } // BuildUraVerifiableCredential constructs a verifiable credential with specified certificates, signing key, subject DID. -func (v DefaultUraIssuer) BuildUraVerifiableCredential(certificates *[]x509.Certificate, signingKey *rsa.PrivateKey, subjectDID string) (*vc.VerifiableCredential, error) { +func BuildUraVerifiableCredential(certificates *[]x509.Certificate, signingKey *rsa.PrivateKey, subjectDID string) (*vc.VerifiableCredential, error) { signingCert, otherNameValue, err := x509_cert.FindSigningCertificate(certificates) if err != nil { return nil, err @@ -125,7 +106,7 @@ func (v DefaultUraIssuer) BuildUraVerifiableCredential(certificates *[]x509.Cert if err != nil { return nil, err } - did, err := v.didCreator.CreateDid(chain) + did, err := did_x509.CreateDid(chain) if err != nil { return nil, err } @@ -184,15 +165,17 @@ func (v DefaultUraIssuer) BuildUraVerifiableCredential(certificates *[]x509.Cert // marshalChain converts a slice of x509.Certificate instances to a cert.Chain, encoding each certificate as PEM. // It returns the PEM-encoded cert.Chain and an error if the encoding or header fixation fails. func marshalChain(certificates *[]x509.Certificate) (*cert.Chain, error) { - chainPems := &cert.Chain{} - for _, certificate := range *certificates { - err := chainPems.Add(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificate.Raw})) + rv := &cert.Chain{} + certs := *certificates + for i, _ := range certs { + certificate := certs[len(certs)-i-1] + err := rv.Add(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificate.Raw})) if err != nil { return nil, err } } - headers, err := x509_cert.FixChainHeaders(chainPems) - return headers, err + rv, err := x509_cert.FixChainHeaders(rv) + return rv, err } func validateChain(certificates *[]x509.Certificate) error { diff --git a/uzi_vc_issuer/ura_issuer_test.go b/uzi_vc_issuer/ura_issuer_test.go index acbdfeb..b213054 100644 --- a/uzi_vc_issuer/ura_issuer_test.go +++ b/uzi_vc_issuer/ura_issuer_test.go @@ -4,16 +4,12 @@ import ( "crypto/rand" "crypto/rsa" "crypto/x509" - "github.com/nuts-foundation/uzi-did-x509-issuer/did_x509" - "github.com/nuts-foundation/uzi-did-x509-issuer/x509_cert" - "go.uber.org/mock/gomock" "math/big" "testing" "time" ) func TestBuildUraVerifiableCredential(t *testing.T) { - ctrl := gomock.NewController(t) privKey, _ := rsa.GenerateKey(rand.Reader, 2048) template := x509.Certificate{ @@ -40,14 +36,11 @@ func TestBuildUraVerifiableCredential(t *testing.T) { }, }, } - creator := did_x509.NewMockDidCreator(ctrl) - parser := x509_cert.NewMockChainParser(ctrl) - builder := NewUraVcBuilder(creator, parser) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { certificates, signingKey, subjectDID := tt.in() - _, err := builder.BuildUraVerifiableCredential(certificates, signingKey, subjectDID) + _, err := BuildUraVerifiableCredential(certificates, signingKey, subjectDID) if got := tt.want(err); !got { t.Errorf("BuildUraVerifiableCredential() error = %v", err) } diff --git a/uzi_vc_validator/ura_validator.go b/uzi_vc_validator/ura_validator.go index 73d4287..a7b4bf5 100644 --- a/uzi_vc_validator/ura_validator.go +++ b/uzi_vc_validator/ura_validator.go @@ -22,8 +22,7 @@ type UraValidator interface { } type UraValidatorImpl struct { - didParser did_x509.DidParser - test bool + test bool } type JwtHeaderValues struct { @@ -39,7 +38,7 @@ func (u UraValidatorImpl) Validate(jwtString string) error { if err != nil { return err } - parseDid, err := u.didParser.ParseDid(credential.Issuer.String()) + parseDid, err := did_x509.ParseDid(credential.Issuer.String()) if err != nil { return err } @@ -174,6 +173,6 @@ func parseJwtHeaderValues(jwtString string) (*JwtHeaderValues, error) { return metadata, nil } -func NewUraValidator(didParser did_x509.DidParser, test bool) *UraValidatorImpl { - return &UraValidatorImpl{didParser, test} +func NewUraValidator(test bool) *UraValidatorImpl { + return &UraValidatorImpl{test} } diff --git a/x509_cert/x509_cert.go b/x509_cert/x509_cert.go index d042b6d..7b61ba4 100644 --- a/x509_cert/x509_cert.go +++ b/x509_cert/x509_cert.go @@ -8,7 +8,9 @@ import ( "crypto/x509" "encoding/asn1" "fmt" + "github.com/lestrrat-go/jwx/v2/cert" "golang.org/x/crypto/sha3" + "strings" ) // SubjectAlternativeNameType represents the ASN.1 Object Identifier for Subject Alternative Name. @@ -40,26 +42,9 @@ func Hash(data []byte, alg string) ([]byte, error) { return nil, fmt.Errorf("unsupported hash algorithm: %s", alg) } -type ChainParser interface { - - // ParseCertificates parses a chain of DER-encoded certificates into an array of x509.Certificate objects. - ParseCertificates(derChain *[][]byte) (*[]x509.Certificate, error) - - // ParsePrivateKey parses a DER-encoded byte slice into an rsa.PrivateKey object, returning an error if parsing fails. - ParsePrivateKey(der *[]byte) (*rsa.PrivateKey, error) -} - -// DefaultChainParser handles the parsing of certificate chains and private keys. -type DefaultChainParser struct{} - -// NewDefaultChainParser creates a new instance of DefaultChainParser. -func NewDefaultChainParser() *DefaultChainParser { - return &DefaultChainParser{} -} - // ParseCertificates parses a slice of DER-encoded byte arrays into a slice of x509.Certificate. // It returns an error if any of the certificates cannot be parsed. -func (c DefaultChainParser) ParseCertificates(derChain *[][]byte) (*[]x509.Certificate, error) { +func ParseCertificates(derChain *[][]byte) (*[]x509.Certificate, error) { if derChain == nil { return nil, fmt.Errorf("derChain is nil") } @@ -78,7 +63,7 @@ func (c DefaultChainParser) ParseCertificates(derChain *[][]byte) (*[]x509.Certi // ParsePrivateKey parses a DER-encoded private key into an *rsa.PrivateKey. // It returns an error if the key is not in PKCS8 format or not an RSA key. -func (c DefaultChainParser) ParsePrivateKey(der *[]byte) (*rsa.PrivateKey, error) { +func ParsePrivateKey(der *[]byte) (*rsa.PrivateKey, error) { if der == nil { return nil, fmt.Errorf("der is nil") } @@ -91,3 +76,18 @@ func (c DefaultChainParser) ParsePrivateKey(der *[]byte) (*rsa.PrivateKey, error } return key.(*rsa.PrivateKey), err } + +// fixChainHeaders replaces newline characters in the certificate chain headers with escaped newline sequences. +// It processes each certificate in the provided chain and returns a new chain with the modified headers or an error if any occurs. +func FixChainHeaders(chain *cert.Chain) (*cert.Chain, error) { + rv := &cert.Chain{} + for i := 0; i < chain.Len(); i++ { + value, _ := chain.Get(i) + der := strings.ReplaceAll(string(value), "\n", "\\n") + err := rv.AddString(der) + if err != nil { + return nil, err + } + } + return rv, nil +} diff --git a/x509_cert/x509_cert_test.go b/x509_cert/x509_cert_test.go index 5e94e90..9686127 100644 --- a/x509_cert/x509_cert_test.go +++ b/x509_cert/x509_cert_test.go @@ -74,8 +74,6 @@ func TestHash(t *testing.T) { } } func TestParseChain(t *testing.T) { - parser := NewDefaultChainParser() - _, chainPem, _, _, _, err := BuildCertChain("9907878") assert.NoError(t, err) derChains := make([][]byte, chainPem.Len()) @@ -109,7 +107,7 @@ func TestParseChain(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - _, err := parser.ParseCertificates(tc.derChain) + _, err := ParseCertificates(tc.derChain) if err != nil { if err.Error() != tc.errMsg { t.Errorf("got error %v, want %v", err, tc.errMsg) @@ -120,7 +118,6 @@ func TestParseChain(t *testing.T) { } func TestParsePrivateKey(t *testing.T) { - parser := NewDefaultChainParser() _, _, _, privateKey, _, err := BuildCertChain("9907878") assert.NoError(t, err) privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) @@ -150,7 +147,7 @@ func TestParsePrivateKey(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - _, err := parser.ParsePrivateKey(tc.der) + _, err := ParsePrivateKey(tc.der) if err != nil { if err.Error() != tc.errMsg { t.Errorf("got error %v, want %v", err, tc.errMsg) diff --git a/x509_cert/x509_test_utils.go b/x509_cert/x509_test_utils.go index 9bf4ce9..8fb50a1 100644 --- a/x509_cert/x509_test_utils.go +++ b/x509_cert/x509_test_utils.go @@ -10,7 +10,6 @@ import ( "fmt" "github.com/lestrrat-go/jwx/v2/cert" "math/big" - "strings" "time" ) @@ -263,18 +262,3 @@ func DebugUnmarshall(data []byte, depth int) error { return nil } - -// fixChainHeaders replaces newline characters in the certificate chain headers with escaped newline sequences. -// It processes each certificate in the provided chain and returns a new chain with the modified headers or an error if any occurs. -func FixChainHeaders(chain *cert.Chain) (*cert.Chain, error) { - rv := &cert.Chain{} - for i := 0; i < chain.Len(); i++ { - value, _ := chain.Get(i) - der := strings.ReplaceAll(string(value), "\n", "\\n") - err := rv.AddString(der) - if err != nil { - return nil, err - } - } - return rv, nil -}