From e2cf89b4377fdfcb7a2c4d1f3bb6c6c6d93eb4d5 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Fri, 13 Dec 2024 11:28:20 +0100 Subject: [PATCH 1/2] Use sha256 instead of sha512 Move Hash function to package where it's used. Make hash alg a constant Use PS256 instead of PS512 for signing --- did_x509/did_x509.go | 57 +++++++++++++---- did_x509/did_x509_test.go | 105 +++++++++++++++++++++++++------ uzi_vc_issuer/ura_issuer.go | 31 ++++----- uzi_vc_issuer/ura_issuer_test.go | 4 +- x509_cert/x509_cert.go | 28 +-------- x509_cert/x509_cert_test.go | 68 +------------------- 6 files changed, 155 insertions(+), 138 deletions(-) diff --git a/did_x509/did_x509.go b/did_x509/did_x509.go index 9e03065..0e55fcb 100644 --- a/did_x509/did_x509.go +++ b/did_x509/did_x509.go @@ -1,6 +1,9 @@ package did_x509 import ( + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" "crypto/x509" "encoding/base64" "errors" @@ -12,8 +15,39 @@ import ( "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/uzi-did-x509-issuer/x509_cert" + "golang.org/x/crypto/sha3" ) +type HashAlg string + +const ( + Sha1 HashAlg = "sha1" + Sha256 HashAlg = "sha256" + Sha384 HashAlg = "sha384" + Sha512 HashAlg = "sha512" +) + +// Hash computes the hash of the input data using the specified algorithm. +// Supported algorithms include "sha1", "sha256", "sha384", and "sha512". +// Returns the computed hash as a byte slice or an error if the algorithm is not supported. +func Hash(data []byte, alg HashAlg) ([]byte, error) { + switch alg { + case Sha1: + sum := sha1.Sum(data) + return sum[:], nil + case Sha256: + sum := sha256.Sum256(data) + return sum[:], nil + case Sha384: + sum := sha3.Sum384(data) + return sum[:], nil + case Sha512: + sum := sha512.Sum512(data) + return sum[:], nil + } + return nil, fmt.Errorf("unsupported hash algorithm: %s", alg) +} + type X509Did struct { Version string RootCertificateHash string @@ -23,35 +57,36 @@ type X509Did struct { // 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(caCert *x509.Certificate, policy ...string) (string, error) { - alg := "sha512" - rootHash, err := x509_cert.Hash(caCert.Raw, alg) +func FormatDid(issuerCert *x509.Certificate, hashAlg HashAlg, policy ...string) (*did.DID, error) { + issuerCertHash, err := Hash(issuerCert.Raw, hashAlg) if err != nil { - return "", err + return nil, err } - encodeToString := base64.RawURLEncoding.EncodeToString(rootHash) - fragments := []string{"did", "x509", "0", alg, encodeToString} - return strings.Join([]string{strings.Join(fragments, ":"), strings.Join(policy, "::")}, "::"), nil + + encodeToString := base64.RawURLEncoding.EncodeToString(issuerCertHash) + fragments := []string{"did", "x509", "0", string(hashAlg), encodeToString} + didString := strings.Join([]string{strings.Join(fragments, ":"), strings.Join(policy, "::")}, "::") + return did.ParseDID(didString) } // 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 CreateDid(signingCert, caCert *x509.Certificate, subjectAttributes []x509_cert.SubjectTypeName, types ...x509_cert.SanTypeName) (string, error) { +func CreateDid(signingCert, caCert *x509.Certificate, subjectAttributes []x509_cert.SubjectTypeName, types ...x509_cert.SanTypeName) (*did.DID, error) { otherNames, err := x509_cert.SelectSanTypes(signingCert, types...) if err != nil { - return "", err + return nil, err } policies := CreateOtherNamePolicies(otherNames) subjectTypes, err := x509_cert.SelectSubjectTypes(signingCert, subjectAttributes...) if err != nil { - return "", err + return nil, err } policies = append(policies, CreateSubjectPolicies(subjectTypes)...) - formattedDid, err := FormatDid(caCert, policies...) + formattedDid, err := FormatDid(caCert, Sha256, policies...) return formattedDid, err } diff --git a/did_x509/did_x509_test.go b/did_x509/did_x509_test.go index b190945..c5806a9 100644 --- a/did_x509/did_x509_test.go +++ b/did_x509/did_x509_test.go @@ -1,15 +1,82 @@ package did_x509 import ( + "bytes" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" "crypto/x509" "encoding/base64" + "fmt" "strings" "testing" + "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/uzi-did-x509-issuer/x509_cert" "github.com/stretchr/testify/assert" + "golang.org/x/crypto/sha3" ) +func TestHash(t *testing.T) { + sha1sum := sha1.Sum([]byte("test")) + sha256sum := sha256.Sum256([]byte("test")) + sha384sum := sha3.Sum384([]byte("test")) + sha512sum := sha512.Sum512([]byte("test")) + testCases := []struct { + name string + data []byte + alg HashAlg + hash []byte + error error + }{ + { + name: "SHA1", + data: []byte("test"), + alg: Sha1, + hash: sha1sum[:], + }, + { + name: "SHA256", + data: []byte("test"), + alg: Sha256, + hash: sha256sum[:], + }, + { + name: "SHA384", + data: []byte("test"), + alg: Sha384, + hash: sha384sum[:], + }, + { + name: "SHA512", + data: []byte("test"), + alg: Sha512, + hash: sha512sum[:], + }, + { + name: "Unsupported", + data: []byte("test"), + alg: "unsupported", + hash: nil, + error: fmt.Errorf("unsupported hash algorithm: %s", "unsupported"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + hash, err := Hash(tc.data, tc.alg) + if tc.error != nil { + if err.Error() != tc.error.Error() { + t.Errorf("unexpected error %v, want %v", err, tc.error) + } + } + if !bytes.Equal(hash, tc.hash) { + t.Errorf("unexpected hash %x, want %x", hash, tc.hash) + } + }) + } +} + func TestPercentEncode(t *testing.T) { tests := []struct { input string @@ -44,8 +111,8 @@ func TestCreateDidSingle(t *testing.T) { t.Fatal(err) } - alg := "sha512" - hash, err := x509_cert.Hash(rootCert.Raw, alg) + alg := Sha256 + hash, err := Hash(rootCert.Raw, alg) if err != nil { t.Fatal(err) } @@ -56,7 +123,7 @@ func TestCreateDidSingle(t *testing.T) { name string fields fields args args - want string + want did.DID errMsg string sanTypes []x509_cert.SanTypeName subjectTypes []x509_cert.SubjectTypeName @@ -65,7 +132,7 @@ func TestCreateDidSingle(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", "", "san", "permanentIdentifier.value", "A_PERMANENT_STRING", "", "san", "permanentIdentifier.assigner", "2.16.528.1.1007.3.3"}, ":"), + want: did.MustParseDID(strings.Join([]string{"did", "x509", "0", string(alg), rootHashString, "", "san", "otherName", "A_BIG_STRING", "", "san", "permanentIdentifier.value", "A_PERMANENT_STRING", "", "san", "permanentIdentifier.assigner", "2.16.528.1.1007.3.3"}, ":")), sanTypes: types, errMsg: "", }, @@ -73,31 +140,31 @@ func TestCreateDidSingle(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", "", "san", "permanentIdentifier.value", "A_PERMANENT_STRING"}, ":"), + want: did.MustParseDID(strings.Join([]string{"did", "x509", "0", string(alg), rootHashString, "", "san", "otherName", "A_BIG_STRING", "", "san", "permanentIdentifier.value", "A_PERMANENT_STRING"}, ":")), sanTypes: []x509_cert.SanTypeName{x509_cert.SanTypeOtherName, x509_cert.SanTypePermanentIdentifierValue}, errMsg: "", }, { - name: "Happy path", + name: "ok - with san othername", fields: fields{}, args: args{chain: chain}, - want: strings.Join([]string{"did", "x509", "0", alg, rootHashString, "", "san", "otherName", "A_BIG_STRING"}, ":"), + want: did.MustParseDID(strings.Join([]string{"did", "x509", "0", string(alg), rootHashString, "", "san", "otherName", "A_BIG_STRING"}, ":")), sanTypes: []x509_cert.SanTypeName{x509_cert.SanTypeOtherName}, errMsg: "", }, { - name: "Happy path", + name: "ok - with san permanentIdentifier.value", fields: fields{}, args: args{chain: chain}, - want: strings.Join([]string{"did", "x509", "0", alg, rootHashString, "", "san", "permanentIdentifier.value", "A_PERMANENT_STRING"}, ":"), + want: did.MustParseDID(strings.Join([]string{"did", "x509", "0", string(alg), rootHashString, "", "san", "permanentIdentifier.value", "A_PERMANENT_STRING"}, ":")), sanTypes: []x509_cert.SanTypeName{x509_cert.SanTypePermanentIdentifierValue}, errMsg: "", }, { - name: "Happy path", + name: "ok - with san permanentIdentifier.assigner", fields: fields{}, args: args{chain: chain}, - want: strings.Join([]string{"did", "x509", "0", alg, rootHashString, "", "san", "permanentIdentifier.assigner", "2.16.528.1.1007.3.3"}, ":"), + want: did.MustParseDID(strings.Join([]string{"did", "x509", "0", string(alg), rootHashString, "", "san", "permanentIdentifier.assigner", "2.16.528.1.1007.3.3"}, ":")), sanTypes: []x509_cert.SanTypeName{x509_cert.SanTypePermanentIdentifierAssigner}, errMsg: "", }, @@ -115,7 +182,7 @@ func TestCreateDidSingle(t *testing.T) { } } - if got != tt.want { + if *got != tt.want { t.Errorf("DefaultDidProcessor.CreateDid() = \n%v\n, want: \n%v\n", got, tt.want) } }) @@ -132,8 +199,8 @@ func TestCreateDidDouble(t *testing.T) { t.Fatal(err) } - alg := "sha512" - hash, err := x509_cert.Hash(rootCert.Raw, alg) + alg := Sha256 + hash, err := Hash(rootCert.Raw, alg) if err != nil { t.Fatal(err) } @@ -155,7 +222,7 @@ func TestCreateDidDouble(t *testing.T) { name: "Happy path san", fields: fields{}, args: args{chain: chain}, - want: strings.Join([]string{"did", "x509", "0", alg, rootHashString, "", "san", "otherName", "A_BIG_STRING", "", "san", "permanentIdentifier.value", "A_SMALL_STRING", "", "san", "permanentIdentifier.assigner", "2.16.528.1.1007.3.3"}, ":"), + want: strings.Join([]string{"did", "x509", "0", string(alg), rootHashString, "", "san", "otherName", "A_BIG_STRING", "", "san", "permanentIdentifier.value", "A_SMALL_STRING", "", "san", "permanentIdentifier.assigner", "2.16.528.1.1007.3.3"}, ":"), sanTypes: sanTypeNames, errMsg: "", }, @@ -163,7 +230,7 @@ func TestCreateDidDouble(t *testing.T) { name: "Happy path short san", 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", string(alg), rootHashString, "", "san", "otherName", "A_BIG_STRING"}, ":"), sanTypes: sanTypeNamesShort, errMsg: "", }, @@ -171,7 +238,7 @@ func TestCreateDidDouble(t *testing.T) { name: "Happy path short san", fields: fields{}, args: args{chain: chain}, - want: strings.Join([]string{"did", "x509", "0", alg, rootHashString, "", "subject", "O", "FauxCare"}, ":"), + want: strings.Join([]string{"did", "x509", "0", string(alg), rootHashString, "", "subject", "O", "FauxCare"}, ":"), subjectTypes: subjectTypeNamesShort, errMsg: "", }, @@ -179,7 +246,7 @@ func TestCreateDidDouble(t *testing.T) { name: "Happy path mixed", fields: fields{}, args: args{chain: chain}, - want: strings.Join([]string{"did", "x509", "0", alg, rootHashString, "", "san", "otherName", "A_BIG_STRING", "", "subject", "O", "FauxCare"}, ":"), + want: strings.Join([]string{"did", "x509", "0", string(alg), rootHashString, "", "san", "otherName", "A_BIG_STRING", "", "subject", "O", "FauxCare"}, ":"), sanTypes: sanTypeNamesShort, subjectTypes: subjectTypeNamesShort, errMsg: "", @@ -198,7 +265,7 @@ func TestCreateDidDouble(t *testing.T) { } } - if got != tt.want { + if got != nil && got.String() != tt.want { t.Errorf("DefaultDidProcessor.CreateDid() = \n%v\n, want: \n%v\n", got, tt.want) } }) diff --git a/uzi_vc_issuer/ura_issuer.go b/uzi_vc_issuer/ura_issuer.go index 1c4f8bb..9ac9b35 100644 --- a/uzi_vc_issuer/ura_issuer.go +++ b/uzi_vc_issuer/ura_issuer.go @@ -119,6 +119,8 @@ var defaultIssueOptions = &issueOptions{ subjectAttributes: []x509_cert.SubjectTypeName{}, } +// NewValidCertificateChain reads a file and returns a valid certificate chain. +// It returns an error if the file does not exist or is empty or the certificates cannot be parsed or the chain is not valid. func NewValidCertificateChain(fileName string) (validCertificateChain, error) { certFileName, err := newFileName(fileName) @@ -148,6 +150,8 @@ func NewValidCertificateChain(fileName string) (validCertificateChain, error) { return chain, nil } +// NewPrivateKey reads a file and returns an RSA private key. +// It returns an error if the file does not exist or is empty or the key cannot be parsed. func NewPrivateKey(fileName string) (privateKey, error) { keyFileName, err := newFileName(fileName) if err != nil { @@ -172,8 +176,12 @@ func NewPrivateKey(fileName string) (privateKey, error) { return key, nil } -func NewSubjectDID(did string) (subjectDID, error) { - return subjectDID(did), nil +func NewSubjectDID(didStr string) (subjectDID, error) { + subject, err := did.ParseDID(didStr) + if err != nil { + return "", err + } + return subjectDID(subject.String()), nil } // newRSAPrivateKey parses a DER-encoded private key into an *rsa.PrivateKey. @@ -210,7 +218,7 @@ func Issue(chain validCertificateChain, key privateKey, subject subjectDID, opti types = append(types, x509_cert.SanTypePermanentIdentifierAssigner) } - did, err := did_x509.CreateDid(chain[0], chain[len(chain)-1], options.subjectAttributes, types...) + issuer, err := did_x509.CreateDid(chain[0], chain[len(chain)-1], options.subjectAttributes, types...) if err != nil { return nil, err } @@ -236,7 +244,7 @@ func Issue(chain validCertificateChain, key privateKey, subject subjectDID, opti if uzi != serialNumber { return nil, errors.New("serial number does not match UZI number") } - template, err := uraCredential(did, signingCert.NotAfter, otherNameValues, subjectTypes, subject) + template, err := uraCredential(*issuer, signingCert.NotAfter, otherNameValues, subjectTypes, subject) if err != nil { return nil, err } @@ -251,7 +259,7 @@ func Issue(chain validCertificateChain, key privateKey, subject subjectDID, opti } if hdrs.KeyID() == "" { - err := hdrs.Set("kid", did+"#0") + err := hdrs.Set("kid", issuer.String()+"#0") if err != nil { return "", err } @@ -274,7 +282,7 @@ func Issue(chain validCertificateChain, key privateKey, subject subjectDID, opti return "", err } - sign, err := jwt.Sign(token, jwt.WithKey(jwa.PS512, rsa.PrivateKey(*key), jws.WithProtectedHeaders(hdrs))) + sign, err := jwt.Sign(token, jwt.WithKey(jwa.PS256, rsa.PrivateKey(*key), jws.WithProtectedHeaders(hdrs))) return string(sign), err }) } @@ -397,7 +405,7 @@ func convertHeaders(headers map[string]interface{}) (jws.Headers, error) { } // uraCredential builds a VerifiableCredential for a given URA and UZI number, including the subject's DID. -func uraCredential(issuer string, expirationDate time.Time, otherNameValues []*x509_cert.OtherNameValue, subjectTypes []*x509_cert.SubjectValue, subjectDID subjectDID) (*vc.VerifiableCredential, error) { +func uraCredential(issuerDID did.DID, expirationDate time.Time, otherNameValues []*x509_cert.OtherNameValue, subjectTypes []*x509_cert.SubjectValue, subjectDID subjectDID) (*vc.VerifiableCredential, error) { iat := time.Now() subject := map[string]interface{}{ "id": subjectDID, @@ -410,17 +418,12 @@ func uraCredential(issuer string, expirationDate time.Time, otherNameValues []*x subject[string(subjectType.Type)] = subjectType.Value } - issuerDID, err := did.ParseDID(issuer) - if err != nil { - return nil, fmt.Errorf("failed to parse issuer DID '%s': %w", issuer, err) - } - id := did.DIDURL{ - DID: *issuerDID, + DID: issuerDID, Fragment: uuid.NewString(), }.URI() return &vc.VerifiableCredential{ - Issuer: ssi.MustParseURI(issuer), + Issuer: issuerDID.URI(), Context: []ssi.URI{ssi.MustParseURI("https://www.w3.org/2018/credentials/v1")}, Type: []ssi.URI{ssi.MustParseURI("VerifiableCredential"), ssi.MustParseURI(CredentialType)}, ID: &id, diff --git a/uzi_vc_issuer/ura_issuer_test.go b/uzi_vc_issuer/ura_issuer_test.go index 3070042..eca1dbc 100644 --- a/uzi_vc_issuer/ura_issuer_test.go +++ b/uzi_vc_issuer/ura_issuer_test.go @@ -187,7 +187,7 @@ func TestIssue(t *testing.T) { assert.Equal(t, "https://www.w3.org/2018/credentials/v1", vc.Context[0].String()) assert.True(t, vc.IsType(ssi.MustParseURI("VerifiableCredential"))) assert.True(t, vc.IsType(ssi.MustParseURI("X509Credential"))) - assert.Equal(t, "did:x509:0:sha512:0OXDVLevEnf_sE-Ayopm0Yof_gmBwxwKZmzbDhKeAwj9vcsI_Q14TBArYsCftQTABLM-Vx9BB6zI05Me2aksaA::san:otherName:2.16.528.1.1007.99.2110-1-1111111-S-2222222-00.000-333333::subject:O:FauxCare", vc.Issuer.String()) + assert.Equal(t, "did:x509:0:sha256:IzvPueXLRjJtLtIicMzV3icpiLQPemu8lBv6oRGjm-o::san:otherName:2.16.528.1.1007.99.2110-1-1111111-S-2222222-00.000-333333::subject:O:FauxCare", vc.Issuer.String()) expectedCredentialSubject := []interface{}([]interface{}{map[string]interface{}{ "id": "did:example:123", @@ -210,7 +210,7 @@ func TestIssue(t *testing.T) { vc, err := Issue(validChain, validKey, "did:example:123", SubjectAttributes(x509_cert.SubjectTypeCountry, x509_cert.SubjectTypeOrganization)) - assert.Equal(t, "did:x509:0:sha512:0OXDVLevEnf_sE-Ayopm0Yof_gmBwxwKZmzbDhKeAwj9vcsI_Q14TBArYsCftQTABLM-Vx9BB6zI05Me2aksaA::san:otherName:2.16.528.1.1007.99.2110-1-1111111-S-2222222-00.000-333333::subject:O:FauxCare%20%26%20Co", vc.Issuer.String()) + assert.Equal(t, "did:x509:0:sha256:IzvPueXLRjJtLtIicMzV3icpiLQPemu8lBv6oRGjm-o::san:otherName:2.16.528.1.1007.99.2110-1-1111111-S-2222222-00.000-333333::subject:O:FauxCare%20%26%20Co", vc.Issuer.String()) }) } diff --git a/x509_cert/x509_cert.go b/x509_cert/x509_cert.go index 6b3578a..56ddc06 100644 --- a/x509_cert/x509_cert.go +++ b/x509_cert/x509_cert.go @@ -2,17 +2,14 @@ package x509_cert import ( "crypto/rsa" - "crypto/sha1" - "crypto/sha256" - "crypto/sha512" "crypto/x509" "encoding/asn1" "errors" "fmt" - "github.com/lestrrat-go/jwx/v2/cert" - "golang.org/x/crypto/sha3" "regexp" "strings" + + "github.com/lestrrat-go/jwx/v2/cert" ) // SubjectAlternativeNameType represents the ASN.1 Object Identifier for Subject Alternative Name. @@ -28,27 +25,6 @@ var ( // var RegexOtherNameValue = regexp.MustCompile(`2\.16\.528\.1\.1007.\d+\.\d+-\d+-\d+-S-(\d+)-00\.000-\d+`) var RegexOtherNameValue = regexp.MustCompile(`^[0-9.]+-\d+-(\d+)-S-(\d+)-00\.000-(\d+)$`) -// Hash computes the hash of the input data using the specified algorithm. -// Supported algorithms include "sha1", "sha256", "sha384", and "sha512". -// Returns the computed hash as a byte slice or an error if the algorithm is not supported. -func Hash(data []byte, alg string) ([]byte, error) { - switch alg { - case "sha1": - sum := sha1.Sum(data) - return sum[:], nil - case "sha256": - sum := sha256.Sum256(data) - return sum[:], nil - case "sha384": - sum := sha3.Sum384(data) - return sum[:], nil - case "sha512": - sum := sha512.Sum512(data) - return sum[:], nil - } - return nil, fmt.Errorf("unsupported hash algorithm: %s", alg) -} - // 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 ParseCertificates(derChain [][]byte) ([]*x509.Certificate, error) { diff --git a/x509_cert/x509_cert_test.go b/x509_cert/x509_cert_test.go index 4f24a19..7893b41 100644 --- a/x509_cert/x509_cert_test.go +++ b/x509_cert/x509_cert_test.go @@ -1,78 +1,14 @@ package x509_cert import ( - "bytes" - "crypto/sha1" - "crypto/sha256" - "crypto/sha512" "crypto/x509" "encoding/pem" - "fmt" - "github.com/stretchr/testify/assert" - "golang.org/x/crypto/sha3" "strings" "testing" -) -func TestHash(t *testing.T) { - sha1sum := sha1.Sum([]byte("test")) - sha256sum := sha256.Sum256([]byte("test")) - sha384sum := sha3.Sum384([]byte("test")) - sha512sum := sha512.Sum512([]byte("test")) - testCases := []struct { - name string - data []byte - alg string - hash []byte - error error - }{ - { - name: "SHA1", - data: []byte("test"), - alg: "sha1", - hash: sha1sum[:], - }, - { - name: "SHA256", - data: []byte("test"), - alg: "sha256", - hash: sha256sum[:], - }, - { - name: "SHA384", - data: []byte("test"), - alg: "sha384", - hash: sha384sum[:], - }, - { - name: "SHA512", - data: []byte("test"), - alg: "sha512", - hash: sha512sum[:], - }, - { - name: "Unsupported", - data: []byte("test"), - alg: "unsupported", - hash: nil, - error: fmt.Errorf("unsupported hash algorithm: %s", "unsupported"), - }, - } + "github.com/stretchr/testify/assert" +) - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - hash, err := Hash(tc.data, tc.alg) - if tc.error != nil { - if err.Error() != tc.error.Error() { - t.Errorf("unexpected error %v, want %v", err, tc.error) - } - } - if !bytes.Equal(hash, tc.hash) { - t.Errorf("unexpected hash %x, want %x", hash, tc.hash) - } - }) - } -} func TestParseChain(t *testing.T) { _, chainPem, _, _, _, err := BuildSelfSignedCertChain("2.16.528.1.1007.99.2110-1-900030787-S-90000380-00.000-11223344", "900030787") failError(t, err) From b43a258ffb83187f19435305c832b31dcc3995c9 Mon Sep 17 00:00:00 2001 From: stevenvegt Date: Fri, 13 Dec 2024 17:19:34 +0100 Subject: [PATCH 2/2] Only use sha256 Removed Hash function with other options --- did_x509/did_x509.go | 50 ++++-------------- did_x509/did_x509_test.go | 106 +++++++------------------------------- 2 files changed, 30 insertions(+), 126 deletions(-) diff --git a/did_x509/did_x509.go b/did_x509/did_x509.go index 0e55fcb..d1a19ae 100644 --- a/did_x509/did_x509.go +++ b/did_x509/did_x509.go @@ -1,9 +1,7 @@ package did_x509 import ( - "crypto/sha1" "crypto/sha256" - "crypto/sha512" "crypto/x509" "encoding/base64" "errors" @@ -15,38 +13,13 @@ import ( "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/uzi-did-x509-issuer/x509_cert" - "golang.org/x/crypto/sha3" ) -type HashAlg string +// hashAlg is the default hash algorithm used for hashing issuerCertificate +const hashAlg = "sha256" -const ( - Sha1 HashAlg = "sha1" - Sha256 HashAlg = "sha256" - Sha384 HashAlg = "sha384" - Sha512 HashAlg = "sha512" -) - -// Hash computes the hash of the input data using the specified algorithm. -// Supported algorithms include "sha1", "sha256", "sha384", and "sha512". -// Returns the computed hash as a byte slice or an error if the algorithm is not supported. -func Hash(data []byte, alg HashAlg) ([]byte, error) { - switch alg { - case Sha1: - sum := sha1.Sum(data) - return sum[:], nil - case Sha256: - sum := sha256.Sum256(data) - return sum[:], nil - case Sha384: - sum := sha3.Sum384(data) - return sum[:], nil - case Sha512: - sum := sha512.Sum512(data) - return sum[:], nil - } - return nil, fmt.Errorf("unsupported hash algorithm: %s", alg) -} +// newHashFn is the default hash function used for hashing issuerCertificate +var newHashFn = sha256.New type X509Did struct { Version string @@ -57,14 +30,13 @@ type X509Did struct { // 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(issuerCert *x509.Certificate, hashAlg HashAlg, policy ...string) (*did.DID, error) { - issuerCertHash, err := Hash(issuerCert.Raw, hashAlg) - if err != nil { - return nil, err - } +func FormatDid(issuerCert *x509.Certificate, policy ...string) (*did.DID, error) { + hasher := newHashFn() + hasher.Write(issuerCert.Raw) + sum := hasher.Sum(nil) - encodeToString := base64.RawURLEncoding.EncodeToString(issuerCertHash) - fragments := []string{"did", "x509", "0", string(hashAlg), encodeToString} + b64EncodedHash := base64.RawURLEncoding.EncodeToString(sum[:]) + fragments := []string{"did", "x509", "0", hashAlg, b64EncodedHash} didString := strings.Join([]string{strings.Join(fragments, ":"), strings.Join(policy, "::")}, "::") return did.ParseDID(didString) } @@ -86,7 +58,7 @@ func CreateDid(signingCert, caCert *x509.Certificate, subjectAttributes []x509_c policies = append(policies, CreateSubjectPolicies(subjectTypes)...) - formattedDid, err := FormatDid(caCert, Sha256, policies...) + formattedDid, err := FormatDid(caCert, policies...) return formattedDid, err } diff --git a/did_x509/did_x509_test.go b/did_x509/did_x509_test.go index c5806a9..0e22552 100644 --- a/did_x509/did_x509_test.go +++ b/did_x509/did_x509_test.go @@ -1,82 +1,16 @@ package did_x509 import ( - "bytes" - "crypto/sha1" - "crypto/sha256" - "crypto/sha512" "crypto/x509" "encoding/base64" - "fmt" "strings" "testing" "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/uzi-did-x509-issuer/x509_cert" "github.com/stretchr/testify/assert" - "golang.org/x/crypto/sha3" ) -func TestHash(t *testing.T) { - sha1sum := sha1.Sum([]byte("test")) - sha256sum := sha256.Sum256([]byte("test")) - sha384sum := sha3.Sum384([]byte("test")) - sha512sum := sha512.Sum512([]byte("test")) - testCases := []struct { - name string - data []byte - alg HashAlg - hash []byte - error error - }{ - { - name: "SHA1", - data: []byte("test"), - alg: Sha1, - hash: sha1sum[:], - }, - { - name: "SHA256", - data: []byte("test"), - alg: Sha256, - hash: sha256sum[:], - }, - { - name: "SHA384", - data: []byte("test"), - alg: Sha384, - hash: sha384sum[:], - }, - { - name: "SHA512", - data: []byte("test"), - alg: Sha512, - hash: sha512sum[:], - }, - { - name: "Unsupported", - data: []byte("test"), - alg: "unsupported", - hash: nil, - error: fmt.Errorf("unsupported hash algorithm: %s", "unsupported"), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - hash, err := Hash(tc.data, tc.alg) - if tc.error != nil { - if err.Error() != tc.error.Error() { - t.Errorf("unexpected error %v, want %v", err, tc.error) - } - } - if !bytes.Equal(hash, tc.hash) { - t.Errorf("unexpected hash %x, want %x", hash, tc.hash) - } - }) - } -} - func TestPercentEncode(t *testing.T) { tests := []struct { input string @@ -111,12 +45,11 @@ func TestCreateDidSingle(t *testing.T) { t.Fatal(err) } - alg := Sha256 - hash, err := Hash(rootCert.Raw, alg) - if err != nil { - t.Fatal(err) - } - rootHashString := base64.RawURLEncoding.EncodeToString(hash) + hash := newHashFn() + hash.Write(rootCert.Raw) + sum := hash.Sum(nil) + + rootHashString := base64.RawURLEncoding.EncodeToString(sum[:]) types := []x509_cert.SanTypeName{x509_cert.SanTypeOtherName, x509_cert.SanTypePermanentIdentifierValue, x509_cert.SanTypePermanentIdentifierAssigner} tests := []struct { @@ -132,7 +65,7 @@ func TestCreateDidSingle(t *testing.T) { name: "Happy path", fields: fields{}, args: args{chain: chain}, - want: did.MustParseDID(strings.Join([]string{"did", "x509", "0", string(alg), rootHashString, "", "san", "otherName", "A_BIG_STRING", "", "san", "permanentIdentifier.value", "A_PERMANENT_STRING", "", "san", "permanentIdentifier.assigner", "2.16.528.1.1007.3.3"}, ":")), + want: did.MustParseDID(strings.Join([]string{"did", "x509", "0", hashAlg, rootHashString, "", "san", "otherName", "A_BIG_STRING", "", "san", "permanentIdentifier.value", "A_PERMANENT_STRING", "", "san", "permanentIdentifier.assigner", "2.16.528.1.1007.3.3"}, ":")), sanTypes: types, errMsg: "", }, @@ -140,7 +73,7 @@ func TestCreateDidSingle(t *testing.T) { name: "Happy path", fields: fields{}, args: args{chain: chain}, - want: did.MustParseDID(strings.Join([]string{"did", "x509", "0", string(alg), rootHashString, "", "san", "otherName", "A_BIG_STRING", "", "san", "permanentIdentifier.value", "A_PERMANENT_STRING"}, ":")), + want: did.MustParseDID(strings.Join([]string{"did", "x509", "0", hashAlg, rootHashString, "", "san", "otherName", "A_BIG_STRING", "", "san", "permanentIdentifier.value", "A_PERMANENT_STRING"}, ":")), sanTypes: []x509_cert.SanTypeName{x509_cert.SanTypeOtherName, x509_cert.SanTypePermanentIdentifierValue}, errMsg: "", }, @@ -148,7 +81,7 @@ func TestCreateDidSingle(t *testing.T) { name: "ok - with san othername", fields: fields{}, args: args{chain: chain}, - want: did.MustParseDID(strings.Join([]string{"did", "x509", "0", string(alg), rootHashString, "", "san", "otherName", "A_BIG_STRING"}, ":")), + want: did.MustParseDID(strings.Join([]string{"did", "x509", "0", hashAlg, rootHashString, "", "san", "otherName", "A_BIG_STRING"}, ":")), sanTypes: []x509_cert.SanTypeName{x509_cert.SanTypeOtherName}, errMsg: "", }, @@ -156,7 +89,7 @@ func TestCreateDidSingle(t *testing.T) { name: "ok - with san permanentIdentifier.value", fields: fields{}, args: args{chain: chain}, - want: did.MustParseDID(strings.Join([]string{"did", "x509", "0", string(alg), rootHashString, "", "san", "permanentIdentifier.value", "A_PERMANENT_STRING"}, ":")), + want: did.MustParseDID(strings.Join([]string{"did", "x509", "0", hashAlg, rootHashString, "", "san", "permanentIdentifier.value", "A_PERMANENT_STRING"}, ":")), sanTypes: []x509_cert.SanTypeName{x509_cert.SanTypePermanentIdentifierValue}, errMsg: "", }, @@ -164,7 +97,7 @@ func TestCreateDidSingle(t *testing.T) { name: "ok - with san permanentIdentifier.assigner", fields: fields{}, args: args{chain: chain}, - want: did.MustParseDID(strings.Join([]string{"did", "x509", "0", string(alg), rootHashString, "", "san", "permanentIdentifier.assigner", "2.16.528.1.1007.3.3"}, ":")), + want: did.MustParseDID(strings.Join([]string{"did", "x509", "0", hashAlg, rootHashString, "", "san", "permanentIdentifier.assigner", "2.16.528.1.1007.3.3"}, ":")), sanTypes: []x509_cert.SanTypeName{x509_cert.SanTypePermanentIdentifierAssigner}, errMsg: "", }, @@ -199,12 +132,11 @@ func TestCreateDidDouble(t *testing.T) { t.Fatal(err) } - alg := Sha256 - hash, err := Hash(rootCert.Raw, alg) - if err != nil { - t.Fatal(err) - } - rootHashString := base64.RawURLEncoding.EncodeToString(hash) + hash := newHashFn() + hash.Write(rootCert.Raw) + sum := hash.Sum(nil) + + rootHashString := base64.RawURLEncoding.EncodeToString(sum[:]) sanTypeNames := []x509_cert.SanTypeName{x509_cert.SanTypeOtherName, x509_cert.SanTypePermanentIdentifierValue, x509_cert.SanTypePermanentIdentifierAssigner} sanTypeNamesShort := []x509_cert.SanTypeName{x509_cert.SanTypeOtherName} subjectTypeNamesShort := []x509_cert.SubjectTypeName{x509_cert.SubjectTypeOrganization} @@ -222,7 +154,7 @@ func TestCreateDidDouble(t *testing.T) { name: "Happy path san", fields: fields{}, args: args{chain: chain}, - want: strings.Join([]string{"did", "x509", "0", string(alg), rootHashString, "", "san", "otherName", "A_BIG_STRING", "", "san", "permanentIdentifier.value", "A_SMALL_STRING", "", "san", "permanentIdentifier.assigner", "2.16.528.1.1007.3.3"}, ":"), + want: strings.Join([]string{"did", "x509", "0", hashAlg, rootHashString, "", "san", "otherName", "A_BIG_STRING", "", "san", "permanentIdentifier.value", "A_SMALL_STRING", "", "san", "permanentIdentifier.assigner", "2.16.528.1.1007.3.3"}, ":"), sanTypes: sanTypeNames, errMsg: "", }, @@ -230,7 +162,7 @@ func TestCreateDidDouble(t *testing.T) { name: "Happy path short san", fields: fields{}, args: args{chain: chain}, - want: strings.Join([]string{"did", "x509", "0", string(alg), rootHashString, "", "san", "otherName", "A_BIG_STRING"}, ":"), + want: strings.Join([]string{"did", "x509", "0", hashAlg, rootHashString, "", "san", "otherName", "A_BIG_STRING"}, ":"), sanTypes: sanTypeNamesShort, errMsg: "", }, @@ -238,7 +170,7 @@ func TestCreateDidDouble(t *testing.T) { name: "Happy path short san", fields: fields{}, args: args{chain: chain}, - want: strings.Join([]string{"did", "x509", "0", string(alg), rootHashString, "", "subject", "O", "FauxCare"}, ":"), + want: strings.Join([]string{"did", "x509", "0", hashAlg, rootHashString, "", "subject", "O", "FauxCare"}, ":"), subjectTypes: subjectTypeNamesShort, errMsg: "", }, @@ -246,7 +178,7 @@ func TestCreateDidDouble(t *testing.T) { name: "Happy path mixed", fields: fields{}, args: args{chain: chain}, - want: strings.Join([]string{"did", "x509", "0", string(alg), rootHashString, "", "san", "otherName", "A_BIG_STRING", "", "subject", "O", "FauxCare"}, ":"), + want: strings.Join([]string{"did", "x509", "0", hashAlg, rootHashString, "", "san", "otherName", "A_BIG_STRING", "", "subject", "O", "FauxCare"}, ":"), sanTypes: sanTypeNamesShort, subjectTypes: subjectTypeNamesShort, errMsg: "",