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

Create self signed chain from command line #1

Merged
merged 15 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from 10 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
22 changes: 11 additions & 11 deletions ca_certs/uzi_ca_certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ func GetCertPools(includeTest bool) (root *x509.CertPool, intermediate *x509.Cer
return downloadUziPool(pool)
}

func GetCerts(includeTest bool) (*[]x509.Certificate, error) {
func GetCerts(includeTest bool) ([]*x509.Certificate, error) {
pool := prepareAndCombinePools(includeTest)
return downloadUziPoolCerts(pool)
}

func GetDERs(includeTest bool) (*[][]byte, error) {
func GetDERs(includeTest bool) ([][]byte, error) {
pool := prepareAndCombinePools(includeTest)
return downloadUziPoolDERs(pool)
}
Expand All @@ -50,19 +50,19 @@ func prepareAndCombinePools(includeTest bool) UziCaPool {
return pool
}

func downloadUziPoolDERs(pool UziCaPool) (*[][]byte, error) {
func downloadUziPoolDERs(pool UziCaPool) ([][]byte, error) {
var rv = [][]byte{}
certs, err := downloadUziPoolCerts(pool)
if err != nil {
return nil, err
}
for _, cert := range *certs {
for _, cert := range certs {
rv = append(rv, cert.Raw)
}
return &rv, err
return rv, err
}

func GetTestCerts() (*[]x509.Certificate, error) {
func GetTestCerts() ([]*x509.Certificate, error) {
return downloadUziPoolCerts(TestUziCaPool)
}

Expand All @@ -81,7 +81,7 @@ func downloadUziPool(pool UziCaPool) (*x509.CertPool, *x509.CertPool, error) {
return roots, intermediates, nil
}

func downloadUziPoolCerts(pool UziCaPool) (*[]x509.Certificate, error) {
func downloadUziPoolCerts(pool UziCaPool) ([]*x509.Certificate, error) {
allUrls := append(pool.rootCaUrls, pool.intermediateCaUrls...)
all, err := downloadCerts(allUrls)
if err != nil {
Expand All @@ -103,16 +103,16 @@ func downloadPool(urls []string) (*x509.CertPool, error) {
return roots, nil
}

func downloadCerts(urls []string) (*[]x509.Certificate, error) {
certs := make([]x509.Certificate, 0)
func downloadCerts(urls []string) ([]*x509.Certificate, error) {
certs := make([]*x509.Certificate, 0)
for _, url := range urls {
certificate, err := readCertificateFromUrl(url)
if err != nil {
return nil, err
}
certs = append(certs, *certificate)
certs = append(certs, certificate)
}
return &certs, nil
return certs, nil
}

func readCertificateFromUrl(url string) (*x509.Certificate, error) {
Expand Down
26 changes: 9 additions & 17 deletions did_x509/did_x509.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,9 @@ 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(chain *[]x509.Certificate, policy string) (string, error) {
root, err := FindRootCertificate(chain)
if err != nil {
return "", err
}
func FormatDid(caCert *x509.Certificate, policy string) (string, error) {
alg := "sha512"
rootHash, err := x509_cert.Hash(root.Raw, alg)
rootHash, err := x509_cert.Hash(caCert.Raw, alg)
if err != nil {
return "", err
}
Expand All @@ -42,17 +38,13 @@ 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 CreateDid(chain *[]x509.Certificate) (string, error) {
certificate, _, err := x509_cert.FindSigningCertificate(chain)
if err != nil || certificate == nil {
return "", err
}
otherNameValue, sanType, err := x509_cert.FindOtherName(certificate)
func CreateDid(signingCert, caCert *x509.Certificate) (string, error) {
otherNameValue, sanType, err := x509_cert.FindOtherName(signingCert)
if err != nil {
return "", err
}
policy := CreatePolicy(otherNameValue, sanType)
formattedDid, err := FormatDid(chain, policy)
formattedDid, err := FormatDid(caCert, policy)
return formattedDid, err
}
func ParseDid(didString string) (*X509Did, error) {
Expand Down Expand Up @@ -83,10 +75,10 @@ func CreatePolicy(otherNameValue string, sanType x509_cert.SanTypeName) string {
}

// FindRootCertificate traverses a chain of x509 certificates and returns the first certificate that is a CA.
func FindRootCertificate(chain *[]x509.Certificate) (*x509.Certificate, error) {
for _, cert := range *chain {
if cert.IsCA {
return &cert, nil
func FindRootCertificate(chain []*x509.Certificate) (*x509.Certificate, error) {
for _, cert := range chain {
if x509_cert.IsRootCa(cert) {
return cert, nil
}
}
return nil, fmt.Errorf("cannot find root certificate")
Expand Down
46 changes: 42 additions & 4 deletions did_x509/did_x509_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 2 additions & 18 deletions did_x509/did_x509_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func TestDefaultDidCreator_CreateDid(t *testing.T) {
type fields struct {
}
type args struct {
chain *[]x509.Certificate
chain []*x509.Certificate
}
chain, _, rootCert, _, _, err := x509_cert.BuildCertChain("A_BIG_STRING")
if err != nil {
Expand All @@ -34,22 +34,6 @@ func TestDefaultDidCreator_CreateDid(t *testing.T) {
want string
errMsg string
}{
{
name: "Test case 1",
fields: fields{},
args: args{chain: &[]x509.Certificate{}},
want: "",
errMsg: "no certificates provided",
},
{
name: "Test case 2",
fields: fields{},
args: args{chain: &[]x509.Certificate{
{},
}},
want: "",
errMsg: "no certificate found in the SAN attributes, please check if the certificate is an UZI Server Certificate",
},
{
name: "Happy path",
fields: fields{},
Expand All @@ -60,7 +44,7 @@ func TestDefaultDidCreator_CreateDid(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CreateDid(tt.args.chain)
got, err := CreateDid(tt.args.chain[0], tt.args.chain[len(tt.args.chain)-1])
wantErr := tt.errMsg != ""
if (err != nil) != wantErr {
t.Errorf("DefaultDidProcessor.CreateDid() error = %v, errMsg %v", err, tt.errMsg)
Expand Down
69 changes: 59 additions & 10 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,27 @@ import (
"fmt"
"github.com/alecthomas/kong"
"github.com/nuts-foundation/uzi-did-x509-issuer/uzi_vc_issuer"
"github.com/nuts-foundation/uzi-did-x509-issuer/x509_cert"
"os"
)

type VC struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some refactoring around the concept op "test" and "self signed". The "test" stuff are certificates signed by the Test UZI Root Ca, the "self signed" stuff you added uses a own CA. To determine the latter case, the code checks if the certificates file is len(c) > 1. In that case it will use other validation logic than if not.

CertificateFile string `arg:"" name:"certificate_file" help:"Certificate PEM file." type:"existingfile"`
CertificateFile string `arg:"" name:"certificate_file" help:"Certificate PEM file. If the file contains a chain, the chain will be used for signing." type:"existingfile"`
SigningKey string `arg:"" name:"signing_key" help:"PEM key for signing." type:"existingfile"`
SubjectDID string `arg:"" name:"subject_did" help:"The subject DID of the VC." type:"key"`
Test bool `short:"t" help:"Allow test certificates."`
Test bool `short:"t" help:"Allow for certificates signed by the TEST UZI Root CA."`
}

type TestCert struct {
Uzi string `arg:"" name:"uzi" help:"The UZI number for the test certificate."`
Ura string `arg:"" name:"ura" help:"The URA number for the test certificate."`
Agb string `arg:"" name:"agb" help:"The AGB code for the test certificate."`
}

var CLI struct {
Version string `help:"Show version."`
Vc VC `cmd:"" help:"Create a new VC."`
Version string `help:"Show version."`
Vc VC `cmd:"" help:"Create a new VC."`
TestCert TestCert `cmd:"" help:"Create a new test certificate."`
}

func main() {
Expand All @@ -25,17 +33,58 @@ func main() {
if err != nil {
panic(err)
}
_, err = parser.Parse(os.Args[1:])
ctx, err := parser.Parse(os.Args[1:])
if err != nil {
parser.FatalIfErrorf(err)
}
vc := cli.Vc
jwt, err := issueVc(vc)
if err != nil {
fmt.Println(err)

switch ctx.Command() {
case "vc <certificate_file> <signing_key> <subject_did>":
vc := cli.Vc
jwt, err := issueVc(vc)
if err != nil {
fmt.Println(err)
os.Exit(-1)
}
println(jwt)
case "test-cert <uzi> <ura> <agb>":
// Format is 2.16.528.1.1007.99.2110-1-900030787-S-90000380-00.000-11223344
// <OID CA>-<versie-nr>-<UZI-nr>-<pastype>-<Abonnee-nr>-<rol>-<AGB-code>
// 2.16.528.1.1007.99.2110-1-<UZI-nr>-S-<Abonnee-nr>-00.000-<AGB-code>
otherName := fmt.Sprintf("2.16.528.1.1007.99.2110-1-%s-S-%s-00.000-%s", cli.TestCert.Uzi, cli.TestCert.Ura, cli.TestCert.Agb)
fmt.Println("Building certificate chain for identifier:", otherName)
chain, _, _, privKey, _, err := x509_cert.BuildCertChain(otherName)
if err != nil {
fmt.Println(err)
os.Exit(-1)
}

chainPems, err := x509_cert.EncodeCertificates(chain...)
if err != nil {
fmt.Println(err)
os.Exit(-1)
}
signingKeyPem, err := x509_cert.EncodeRSAPrivateKey(privKey)
if err != nil {
fmt.Println(err)
os.Exit(-1)
}

err = os.WriteFile("chain.pem", chainPems, 0644)
if err != nil {
fmt.Println(err)
os.Exit(-1)
}
err = os.WriteFile("signing_key.pem", signingKeyPem, 0644)
if err != nil {
fmt.Println(err)
os.Exit(-1)
}

default:
fmt.Println("Unknown command")
os.Exit(-1)
}
println(jwt)
}

func issueVc(vc VC) (string, error) {
Expand Down
18 changes: 10 additions & 8 deletions pem/pem_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package pem

import (
"encoding/pem"
"fmt"
"os"
)

// ParseFileOrPath processes a file or directory at the given path and extracts PEM blocks of the specified pemType.
func 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
Expand All @@ -25,9 +26,9 @@ func ParseFileOrPath(path string, pemType string) (*[][]byte, error) {
if err != nil {
return nil, err
}
files = append(files, *blocks...)
files = append(files, blocks...)
}
return &files, nil
return files, nil
} else {
blocks, err := readFile(path, pemType)
return blocks, err
Expand All @@ -36,21 +37,22 @@ func ParseFileOrPath(path string, pemType string) (*[][]byte, error) {
}

// readFile reads a file from the given filename, parses it for PEM blocks of the specified type, and returns the blocks.
func readFile(filename string, pemType string) (*[][]byte, error) {
func readFile(filename string, pemType string) ([][]byte, error) {
fmt.Println("filename: ", filename)
files := make([][]byte, 0)
content, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
if looksLineCert(content, pemType) {
foundBlocks := ParsePemBlocks(content, pemType)
files = append(files, *foundBlocks...)
files = append(files, foundBlocks...)
}
return &files, nil
return files, nil
}

// ParsePemBlocks extracts specified PEM blocks from the provided certificate bytes and returns them as a pointer to a slice of byte slices.
func ParsePemBlocks(cert []byte, pemType string) *[][]byte {
func ParsePemBlocks(cert []byte, pemType string) [][]byte {
blocks := make([][]byte, 0)
for {
pemBlock, tail := pem.Decode(cert)
Expand All @@ -66,7 +68,7 @@ func ParsePemBlocks(cert []byte, pemType string) *[][]byte {
cert = tail

}
return &blocks
return blocks
}

// looksLineCert checks if the given certificate data is a valid PEM block of the specified type.
Expand Down
Loading
Loading