Skip to content

Commit abe731e

Browse files
Create self signed chain from command line (#1)
* Create test chain from command line Add functionality to create a test chain from the commandline Refactor things: - Cert chain always in fixed order - `*[]x509.Certificate` -> `[]*x509.Certificate` - `*[][]byte` -> `[][]byte` * Prevented infinite loop - Multiple CA roots co-exist when the -t flag is enabled. * Remove debug print statements from findOtherNameValue Removed two debug print statements that printed extension IDs and other name types. These statements were cluttering the output and are not necessary for the final implementation. * Refactor certificate validation to support test and self-signed CAs Introduce `allowUziTestCa` and `allowSelfSignedCa` flags to UraValidatorImpl for more flexible certificate chain validation. Refactor the validation logic to handle these new flags appropriately, ensuring better support for various CA configurations. * Fix certificate lookup in validator Re-enable the use of findSigningCertificate to ensure proper certificate validation based on X509 thumbprint. Remove hardcoding of the signingCert as first certificate in the chain and handle potential errors appropriately. * Update test-cert CLI to include UZI, URA, and AGB parameters Improved the test-cert command to specify UZI, URA, and AGB parameters instead of a single identifier. This change enhances flexibility and allows for a more precise definition of test certificates. Updated help text for better clarity and added comments to explain the format of the otherName field. * Handle file write errors separately Previously, errors when writing "chain.pem" and "signing_key.pem" were not handled separately. This change ensures that each file write operation checks for errors independently and exits with an error message if a write fails. * Rename variables and use helper function for root CA check Updated `FormatDid` function to use a more descriptive variable `caCert` instead of `ca`. Modified the `FindRootCertificate` function to use the `x509_cert.IsRootCa` helper function for improved readability and consistency in the root CA check. * Bugfix: Rename and update CertTemplate to include organization. The Name seems to be the field that identifies the signer and subject. Updated CertTemplate function to accept an additional "organization" parameter, which allows different organization names for certificates. Adjusted related calls and modified default certificate values to reflect these changes. * Add `subject_did` field and update `test-cert` case Introduce a new `subject_did` field in the `TestCert` struct with a default value. Update the `test-cert` command case to handle this new parameter and adjust the VC issuance logic. * Potentially unsafe quoting: Refactor JSON unmarshal logic in Validate method Replaced the string formatting approach with direct JSON marshaling to prevent potentially unsafe quoting. * Refactor variable name in EncodeCertificates function Renamed the loop variable from 'cert' to 'c' to prevent a naming collision with the cert import. * Add a comment to keep DebugUnmarshall for future debugging A comment was added to the DebugUnmarshall method to indicate that it should be retained for future debugging purposes. --------- Co-authored-by: Roland Groen <[email protected]>
1 parent ef8021f commit abe731e

File tree

15 files changed

+358
-239
lines changed

15 files changed

+358
-239
lines changed

ca_certs/uzi_ca_certs.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ func GetCertPools(includeTest bool) (root *x509.CertPool, intermediate *x509.Cer
2929
return downloadUziPool(pool)
3030
}
3131

32-
func GetCerts(includeTest bool) (*[]x509.Certificate, error) {
32+
func GetCerts(includeTest bool) ([]*x509.Certificate, error) {
3333
pool := prepareAndCombinePools(includeTest)
3434
return downloadUziPoolCerts(pool)
3535
}
3636

37-
func GetDERs(includeTest bool) (*[][]byte, error) {
37+
func GetDERs(includeTest bool) ([][]byte, error) {
3838
pool := prepareAndCombinePools(includeTest)
3939
return downloadUziPoolDERs(pool)
4040
}
@@ -50,19 +50,19 @@ func prepareAndCombinePools(includeTest bool) UziCaPool {
5050
return pool
5151
}
5252

53-
func downloadUziPoolDERs(pool UziCaPool) (*[][]byte, error) {
53+
func downloadUziPoolDERs(pool UziCaPool) ([][]byte, error) {
5454
var rv = [][]byte{}
5555
certs, err := downloadUziPoolCerts(pool)
5656
if err != nil {
5757
return nil, err
5858
}
59-
for _, cert := range *certs {
59+
for _, cert := range certs {
6060
rv = append(rv, cert.Raw)
6161
}
62-
return &rv, err
62+
return rv, err
6363
}
6464

65-
func GetTestCerts() (*[]x509.Certificate, error) {
65+
func GetTestCerts() ([]*x509.Certificate, error) {
6666
return downloadUziPoolCerts(TestUziCaPool)
6767
}
6868

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

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

106-
func downloadCerts(urls []string) (*[]x509.Certificate, error) {
107-
certs := make([]x509.Certificate, 0)
106+
func downloadCerts(urls []string) ([]*x509.Certificate, error) {
107+
certs := make([]*x509.Certificate, 0)
108108
for _, url := range urls {
109109
certificate, err := readCertificateFromUrl(url)
110110
if err != nil {
111111
return nil, err
112112
}
113-
certs = append(certs, *certificate)
113+
certs = append(certs, certificate)
114114
}
115-
return &certs, nil
115+
return certs, nil
116116
}
117117

118118
func readCertificateFromUrl(url string) (*x509.Certificate, error) {

did_x509/did_x509.go

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,9 @@ type X509Did struct {
2121

2222
// FormatDid constructs a decentralized identifier (DID) from a certificate chain and an optional policy.
2323
// It returns the formatted DID string or an error if the root certificate or hash calculation fails.
24-
func FormatDid(chain *[]x509.Certificate, policy string) (string, error) {
25-
root, err := FindRootCertificate(chain)
26-
if err != nil {
27-
return "", err
28-
}
24+
func FormatDid(caCert *x509.Certificate, policy string) (string, error) {
2925
alg := "sha512"
30-
rootHash, err := x509_cert.Hash(root.Raw, alg)
26+
rootHash, err := x509_cert.Hash(caCert.Raw, alg)
3127
if err != nil {
3228
return "", err
3329
}
@@ -42,17 +38,13 @@ func FormatDid(chain *[]x509.Certificate, policy string) (string, error) {
4238
// CreateDid generates a Decentralized Identifier (DID) from a given certificate chain.
4339
// It extracts the Unique Registration Address (URA) from the chain, creates a policy with it, and formats the DID.
4440
// Returns the generated DID or an error if any step fails.
45-
func CreateDid(chain *[]x509.Certificate) (string, error) {
46-
certificate, _, err := x509_cert.FindSigningCertificate(chain)
47-
if err != nil || certificate == nil {
48-
return "", err
49-
}
50-
otherNameValue, sanType, err := x509_cert.FindOtherName(certificate)
41+
func CreateDid(signingCert, caCert *x509.Certificate) (string, error) {
42+
otherNameValue, sanType, err := x509_cert.FindOtherName(signingCert)
5143
if err != nil {
5244
return "", err
5345
}
5446
policy := CreatePolicy(otherNameValue, sanType)
55-
formattedDid, err := FormatDid(chain, policy)
47+
formattedDid, err := FormatDid(caCert, policy)
5648
return formattedDid, err
5749
}
5850
func ParseDid(didString string) (*X509Did, error) {
@@ -83,10 +75,10 @@ func CreatePolicy(otherNameValue string, sanType x509_cert.SanTypeName) string {
8375
}
8476

8577
// FindRootCertificate traverses a chain of x509 certificates and returns the first certificate that is a CA.
86-
func FindRootCertificate(chain *[]x509.Certificate) (*x509.Certificate, error) {
87-
for _, cert := range *chain {
88-
if cert.IsCA {
89-
return &cert, nil
78+
func FindRootCertificate(chain []*x509.Certificate) (*x509.Certificate, error) {
79+
for _, cert := range chain {
80+
if x509_cert.IsRootCa(cert) {
81+
return cert, nil
9082
}
9183
}
9284
return nil, fmt.Errorf("cannot find root certificate")

did_x509/did_x509_mock.go

Lines changed: 42 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

did_x509/did_x509_test.go

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ func TestDefaultDidCreator_CreateDid(t *testing.T) {
1414
type fields struct {
1515
}
1616
type args struct {
17-
chain *[]x509.Certificate
17+
chain []*x509.Certificate
1818
}
1919
chain, _, rootCert, _, _, err := x509_cert.BuildCertChain("A_BIG_STRING")
2020
if err != nil {
@@ -34,22 +34,6 @@ func TestDefaultDidCreator_CreateDid(t *testing.T) {
3434
want string
3535
errMsg string
3636
}{
37-
{
38-
name: "Test case 1",
39-
fields: fields{},
40-
args: args{chain: &[]x509.Certificate{}},
41-
want: "",
42-
errMsg: "no certificates provided",
43-
},
44-
{
45-
name: "Test case 2",
46-
fields: fields{},
47-
args: args{chain: &[]x509.Certificate{
48-
{},
49-
}},
50-
want: "",
51-
errMsg: "no certificate found in the SAN attributes, please check if the certificate is an UZI Server Certificate",
52-
},
5337
{
5438
name: "Happy path",
5539
fields: fields{},
@@ -60,7 +44,7 @@ func TestDefaultDidCreator_CreateDid(t *testing.T) {
6044
}
6145
for _, tt := range tests {
6246
t.Run(tt.name, func(t *testing.T) {
63-
got, err := CreateDid(tt.args.chain)
47+
got, err := CreateDid(tt.args.chain[0], tt.args.chain[len(tt.args.chain)-1])
6448
wantErr := tt.errMsg != ""
6549
if (err != nil) != wantErr {
6650
t.Errorf("DefaultDidProcessor.CreateDid() error = %v, errMsg %v", err, tt.errMsg)

main.go

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,28 @@ import (
44
"fmt"
55
"github.com/alecthomas/kong"
66
"github.com/nuts-foundation/uzi-did-x509-issuer/uzi_vc_issuer"
7+
"github.com/nuts-foundation/uzi-did-x509-issuer/x509_cert"
78
"os"
89
)
910

1011
type VC struct {
11-
CertificateFile string `arg:"" name:"certificate_file" help:"Certificate PEM file." type:"existingfile"`
12+
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"`
1213
SigningKey string `arg:"" name:"signing_key" help:"PEM key for signing." type:"existingfile"`
1314
SubjectDID string `arg:"" name:"subject_did" help:"The subject DID of the VC." type:"key"`
14-
Test bool `short:"t" help:"Allow test certificates."`
15+
Test bool `short:"t" help:"Allow for certificates signed by the TEST UZI Root CA."`
16+
}
17+
18+
type TestCert struct {
19+
Uzi string `arg:"" name:"uzi" help:"The UZI number for the test certificate."`
20+
Ura string `arg:"" name:"ura" help:"The URA number for the test certificate."`
21+
Agb string `arg:"" name:"agb" help:"The AGB code for the test certificate."`
22+
SubjectDID string `arg:"" default:"did:web:example.com:test" name:"subject_did" help:"The subject DID of the VC." type:"key"`
1523
}
1624

1725
var CLI struct {
18-
Version string `help:"Show version."`
19-
Vc VC `cmd:"" help:"Create a new VC."`
26+
Version string `help:"Show version."`
27+
Vc VC `cmd:"" help:"Create a new VC."`
28+
TestCert TestCert `cmd:"" help:"Create a new test certificate."`
2029
}
2130

2231
func main() {
@@ -25,17 +34,69 @@ func main() {
2534
if err != nil {
2635
panic(err)
2736
}
28-
_, err = parser.Parse(os.Args[1:])
37+
ctx, err := parser.Parse(os.Args[1:])
2938
if err != nil {
3039
parser.FatalIfErrorf(err)
3140
}
32-
vc := cli.Vc
33-
jwt, err := issueVc(vc)
34-
if err != nil {
35-
fmt.Println(err)
41+
42+
switch ctx.Command() {
43+
case "vc <certificate_file> <signing_key> <subject_did>":
44+
vc := cli.Vc
45+
jwt, err := issueVc(vc)
46+
if err != nil {
47+
fmt.Println(err)
48+
os.Exit(-1)
49+
}
50+
println(jwt)
51+
case "test-cert <uzi> <ura> <agb>", "test-cert <uzi> <ura> <agb> <subject_did>":
52+
// Format is 2.16.528.1.1007.99.2110-1-900030787-S-90000380-00.000-11223344
53+
// <OID CA>-<versie-nr>-<UZI-nr>-<pastype>-<Abonnee-nr>-<rol>-<AGB-code>
54+
// 2.16.528.1.1007.99.2110-1-<UZI-nr>-S-<Abonnee-nr>-00.000-<AGB-code>
55+
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)
56+
fmt.Println("Building certificate chain for identifier:", otherName)
57+
chain, _, _, privKey, _, err := x509_cert.BuildCertChain(otherName)
58+
if err != nil {
59+
fmt.Println(err)
60+
os.Exit(-1)
61+
}
62+
63+
chainPems, err := x509_cert.EncodeCertificates(chain...)
64+
if err != nil {
65+
fmt.Println(err)
66+
os.Exit(-1)
67+
}
68+
signingKeyPem, err := x509_cert.EncodeRSAPrivateKey(privKey)
69+
if err != nil {
70+
fmt.Println(err)
71+
os.Exit(-1)
72+
}
73+
74+
err = os.WriteFile("chain.pem", chainPems, 0644)
75+
if err != nil {
76+
fmt.Println(err)
77+
os.Exit(-1)
78+
}
79+
err = os.WriteFile("signing_key.pem", signingKeyPem, 0644)
80+
if err != nil {
81+
fmt.Println(err)
82+
os.Exit(-1)
83+
}
84+
vc := VC{
85+
CertificateFile: "chain.pem",
86+
SigningKey: "signing_key.pem",
87+
SubjectDID: cli.TestCert.SubjectDID,
88+
Test: false,
89+
}
90+
jwt, err := issueVc(vc)
91+
if err != nil {
92+
fmt.Println(err)
93+
os.Exit(-1)
94+
}
95+
println(jwt)
96+
default:
97+
fmt.Println("Unknown command")
3698
os.Exit(-1)
3799
}
38-
println(jwt)
39100
}
40101

41102
func issueVc(vc VC) (string, error) {

pem/pem_reader.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ package pem
22

33
import (
44
"encoding/pem"
5+
"fmt"
56
"os"
67
)
78

89
// ParseFileOrPath processes a file or directory at the given path and extracts PEM blocks of the specified pemType.
9-
func ParseFileOrPath(path string, pemType string) (*[][]byte, error) {
10+
func ParseFileOrPath(path string, pemType string) ([][]byte, error) {
1011
fileInfo, err := os.Stat(path)
1112
if err != nil {
1213
return nil, err
@@ -25,9 +26,9 @@ func ParseFileOrPath(path string, pemType string) (*[][]byte, error) {
2526
if err != nil {
2627
return nil, err
2728
}
28-
files = append(files, *blocks...)
29+
files = append(files, blocks...)
2930
}
30-
return &files, nil
31+
return files, nil
3132
} else {
3233
blocks, err := readFile(path, pemType)
3334
return blocks, err
@@ -36,21 +37,22 @@ func ParseFileOrPath(path string, pemType string) (*[][]byte, error) {
3637
}
3738

3839
// readFile reads a file from the given filename, parses it for PEM blocks of the specified type, and returns the blocks.
39-
func readFile(filename string, pemType string) (*[][]byte, error) {
40+
func readFile(filename string, pemType string) ([][]byte, error) {
41+
fmt.Println("filename: ", filename)
4042
files := make([][]byte, 0)
4143
content, err := os.ReadFile(filename)
4244
if err != nil {
4345
return nil, err
4446
}
4547
if looksLineCert(content, pemType) {
4648
foundBlocks := ParsePemBlocks(content, pemType)
47-
files = append(files, *foundBlocks...)
49+
files = append(files, foundBlocks...)
4850
}
49-
return &files, nil
51+
return files, nil
5052
}
5153

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

6870
}
69-
return &blocks
71+
return blocks
7072
}
7173

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

0 commit comments

Comments
 (0)