Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use sha256 instead of sha512 #38

Merged
merged 2 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions did_x509/did_x509.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package did_x509

import (
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"errors"
Expand All @@ -14,6 +15,12 @@ import (
"github.com/nuts-foundation/uzi-did-x509-issuer/x509_cert"
)

// hashAlg is the default hash algorithm used for hashing issuerCertificate
const hashAlg = "sha256"

// newHashFn is the default hash function used for hashing issuerCertificate
var newHashFn = sha256.New

type X509Did struct {
Version string
RootCertificateHash string
Expand All @@ -23,30 +30,30 @@ 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)
if err != nil {
return "", err
}
encodeToString := base64.RawURLEncoding.EncodeToString(rootHash)
fragments := []string{"did", "x509", "0", alg, encodeToString}
return strings.Join([]string{strings.Join(fragments, ":"), strings.Join(policy, "::")}, "::"), nil
func FormatDid(issuerCert *x509.Certificate, policy ...string) (*did.DID, error) {
hasher := newHashFn()
hasher.Write(issuerCert.Raw)
sum := hasher.Sum(nil)

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)
}

// 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)...)
Expand Down
53 changes: 26 additions & 27 deletions did_x509/did_x509_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"
"testing"

"github.com/nuts-foundation/go-did/did"
"github.com/nuts-foundation/uzi-did-x509-issuer/x509_cert"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -44,19 +45,18 @@ func TestCreateDidSingle(t *testing.T) {
t.Fatal(err)
}

alg := "sha512"
hash, err := x509_cert.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 {
name string
fields fields
args args
want string
want did.DID
errMsg string
sanTypes []x509_cert.SanTypeName
subjectTypes []x509_cert.SubjectTypeName
Expand All @@ -65,39 +65,39 @@ 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", 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: "",
},
{
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", hashAlg, 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", hashAlg, 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", hashAlg, 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", hashAlg, rootHashString, "", "san", "permanentIdentifier.assigner", "2.16.528.1.1007.3.3"}, ":")),
sanTypes: []x509_cert.SanTypeName{x509_cert.SanTypePermanentIdentifierAssigner},
errMsg: "",
},
Expand All @@ -115,7 +115,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)
}
})
Expand All @@ -132,12 +132,11 @@ func TestCreateDidDouble(t *testing.T) {
t.Fatal(err)
}

alg := "sha512"
hash, err := x509_cert.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}
Expand All @@ -155,31 +154,31 @@ 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", 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: "",
},
{
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", hashAlg, rootHashString, "", "san", "otherName", "A_BIG_STRING"}, ":"),
sanTypes: sanTypeNamesShort,
errMsg: "",
},
{
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", hashAlg, rootHashString, "", "subject", "O", "FauxCare"}, ":"),
subjectTypes: subjectTypeNamesShort,
errMsg: "",
},
{
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", hashAlg, rootHashString, "", "san", "otherName", "A_BIG_STRING", "", "subject", "O", "FauxCare"}, ":"),
sanTypes: sanTypeNamesShort,
subjectTypes: subjectTypeNamesShort,
errMsg: "",
Expand All @@ -198,7 +197,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)
}
})
Expand Down
31 changes: 17 additions & 14 deletions uzi_vc_issuer/ura_issuer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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 {
Expand All @@ -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.
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
})
}
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions uzi_vc_issuer/ura_issuer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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())
})

}
Expand Down
28 changes: 2 additions & 26 deletions x509_cert/x509_cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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) {
Expand Down
Loading
Loading