diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..62d5dda --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,76 @@ +--- +run: + skip-files: + - pkg/config/amd64.go + - pkg/config/arm64.go + - pkg/config/armhf.go + +output: + sort-results: true + +linters-settings: + cyclop: + max-complexity: 15 + goheader: + values: + const: + ORGANIZATION: CAcert Inc. + template: |- + Copyright {{ YEAR-RANGE }} {{ ORGANIZATION }} + SPDX-License-Identifier: Apache-2.0 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + gomnd: + ignored-functions: + - 'strconv.*' + ignored-numbers: ["2", "16", "128", "0o600"] + goimports: + local-prefixes: code.cacert.org,git.cacert.org + misspell: + locale: US + ignore-words: + - CAcert + +linters: + disable-all: false + enable: + - bodyclose + - containedctx + - contextcheck + - cyclop + - decorder + - errorlint + - exportloopref + - forbidigo + - forcetypeassert + - gocognit + - goconst + - gocritic + - gofmt + - goheader + - goimports + - gomnd + - gosec + - lll + - makezero + - misspell + - nakedret + - nestif + - nlreturn + - nolintlint + - predeclared + - revive + - rowserrcheck + - sqlclosecheck + - wrapcheck + - wsl diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/cmd/signer/main.go b/cmd/signer/main.go index c8e9d5c..cbf278d 100644 --- a/cmd/signer/main.go +++ b/cmd/signer/main.go @@ -1,3 +1,20 @@ +/* +Copyright 2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package main import ( @@ -51,10 +68,12 @@ func main() { if err != nil { log.Fatalf("could not load CA hierarchy: %v", err) } + opts = append(opts, hsm.CaConfigOption(caConfig)) if setupMode { log.Print("running in setup mode") + opts = append(opts, hsm.SetupModeOption()) } diff --git a/pkg/config/amd64.go b/pkg/config/amd64.go index 8e5103c..b5bec50 100644 --- a/pkg/config/amd64.go +++ b/pkg/config/amd64.go @@ -1,5 +1,22 @@ //go:build linux && amd64 +/* +Copyright 2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package config const SoftHsmModule = "/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so" diff --git a/pkg/config/arm64.go b/pkg/config/arm64.go index 11f1ad6..02ccd8e 100644 --- a/pkg/config/arm64.go +++ b/pkg/config/arm64.go @@ -1,5 +1,22 @@ //go:build linux && arm64 +/* +Copyright 2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package config const SoftHsmModule = "/usr/lib/aarch64-linux-gnu/softhsm/libsofthsm2.so" diff --git a/pkg/config/armhf.go b/pkg/config/armhf.go index 9a6fbe1..ec7a214 100644 --- a/pkg/config/armhf.go +++ b/pkg/config/armhf.go @@ -1,5 +1,22 @@ //go:build linux && arm +/* +Copyright 2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package config const SoftHsmModule = "/usr/lib/arm-linux-gnueabihf/softhsm/libsofthsm2.so" diff --git a/pkg/config/config.go b/pkg/config/config.go index 97a8ab7..85f8d7b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,3 +1,20 @@ +/* +Copyright 2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package config import ( @@ -66,12 +83,14 @@ func (s *Settings) UnmarshalYAML(n *yaml.Node) error { } if data.ValidityYears.Root < data.ValidityYears.Intermediary { - return SettingsError{"validity of root CA certificates must be equal or greater than those if intermediary CA certificates"} + return SettingsError{"validity of root CA certificates must be equal or greater than those if" + + " intermediary CA certificates"} } if data.URLPatterns.Ocsp == "" { return SettingsError{"you must specify an 'ocsp' URL pattern"} } + if strings.Count(data.URLPatterns.Ocsp, "%s") > 1 { return SettingsError{"url-pattern 'ocsp' must contain zero or one '%s' placeholder"} } @@ -79,6 +98,7 @@ func (s *Settings) UnmarshalYAML(n *yaml.Node) error { if data.URLPatterns.CRL == "" { return SettingsError{"you must specify an 'crl' URL pattern"} } + if strings.Count(data.URLPatterns.CRL, "%s") != 1 { return SettingsError{"url-pattern 'crl' must contain one '%s' placeholder"} } @@ -86,6 +106,7 @@ func (s *Settings) UnmarshalYAML(n *yaml.Node) error { if data.URLPatterns.Issuer == "" { return SettingsError{"you must specify an 'issuer' URL pattern"} } + if strings.Count(data.URLPatterns.Issuer, "%s") != 1 { return SettingsError{"url-pattern 'issuer' must contain one '%s' placeholder"} } @@ -131,6 +152,7 @@ func (k *KeyStorage) UnmarshalYAML(n *yaml.Node) error { if ks.Module == "" { return errors.New("specify a 'module' field when using the 'p11module' type") } + k.Module = ks.Module default: return fmt.Errorf("unsupported KeyStorage type '%s'", ks.TokenType) @@ -139,6 +161,7 @@ func (k *KeyStorage) UnmarshalYAML(n *yaml.Node) error { if ks.Label == "" { return errors.New("element 'label' must be specified") } + k.Label = ks.Label return nil @@ -155,6 +178,7 @@ func (c *SignerConfig) GetCADefinition(label string) (*CaCertificateEntry, error if !ok { return nil, fmt.Errorf("no CA definition found for label %s", label) } + return entry, nil } @@ -167,6 +191,7 @@ func (c *SignerConfig) CalculateValidity(cert *CaCertificateEntry, relativeTo ti } else { notAfter = notBefore.AddDate(c.global.ValidityYears.Intermediary, 0, 0) } + return notBefore, notAfter } @@ -179,6 +204,7 @@ func (c *SignerConfig) CalculateSubject(cert *CaCertificateEntry) pkix.Name { PostalCode: c.global.Organization.PostalCode, } subject.CommonName = cert.CommonName + return subject } @@ -195,6 +221,7 @@ func (c *SignerConfig) BuildOCSPURL(cert *CaCertificateEntry) string { if strings.Count(c.global.URLPatterns.Ocsp, "%s") == 1 { return fmt.Sprintf(c.global.URLPatterns.Ocsp, cert.Parent) } + return c.global.URLPatterns.Ocsp } @@ -218,6 +245,7 @@ func (c *SignerConfig) GetParentCA(label string) (*CaCertificateEntry, error) { // RootCAs returns the labels of all configured root CAs func (c *SignerConfig) RootCAs() []string { roots := make([]string, 0) + for label, entry := range c.caMap { if entry.IsRoot() { roots = append(roots, label) @@ -230,6 +258,7 @@ func (c *SignerConfig) RootCAs() []string { // IntermediaryCAs returns the labels of all configured intermediary CAs func (c *SignerConfig) IntermediaryCAs() []string { intermediaries := make([]string, 0) + for label, entry := range c.caMap { if !entry.IsRoot() { intermediaries = append(intermediaries, label) @@ -294,15 +323,19 @@ func (p *PrivateKeyInfo) UnmarshalYAML(value *yaml.Node) error { switch internalStructure.Algorithm { case "RSA": p.Algorithm = x509.RSA + if internalStructure.RSABits == nil { return errors.New("element 'rsa-bits' with RSA key length required for algorithm RSA") } + p.RSABits = *internalStructure.RSABits case "EC": p.Algorithm = x509.ECDSA + if internalStructure.EccCurve == "" { return errors.New("element 'ecc-curve' required for algorithm EC") } + p.EccCurve, err = nameToCurve(internalStructure.EccCurve) if err != nil { return err @@ -325,16 +358,19 @@ func (p *PrivateKeyInfo) MarshalYAML() (interface{}, error) { EccCurve string `yaml:"ecc-curve,omitempty"` RSABits *int `yaml:"rsa-bits,omitempty"` }{} + switch p.Algorithm { case x509.RSA: internalStructure.Algorithm = "RSA" internalStructure.RSABits = &p.RSABits case x509.ECDSA: internalStructure.Algorithm = "EC" + curveName, err := curveToName(p.EccCurve) if err != nil { return nil, err } + internalStructure.EccCurve = curveName } @@ -385,17 +421,18 @@ type CaCertificateEntry struct { func (c *CaCertificateEntry) UnmarshalYAML(value *yaml.Node) error { var m struct { - KeyInfo *PrivateKeyInfo `yaml:"key-info"` - CommonName string `yaml:"common-name"` - MaxPathLen int `yaml:"max-path-len,omitempty"` // maximum path length should be 0 for CAs that issue end entity certificates - ExtKeyUsage []string `yaml:"ext-key-usages,omitempty"` - Parent string `yaml:"parent"` - Storage string `yaml:"storage"` + KeyInfo *PrivateKeyInfo `yaml:"key-info"` + CommonName string `yaml:"common-name"` + // maximum path length should be 0 for CAs that issue end entity certificates + MaxPathLen int `yaml:"max-path-len,omitempty"` + ExtKeyUsage []string `yaml:"ext-key-usages,omitempty"` + Parent string `yaml:"parent"` + Storage string `yaml:"storage"` } err := value.Decode(&m) if err != nil { - return err + return fmt.Errorf("could not unmarshal CA certificate entry: %w", err) } if m.KeyInfo == nil { diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index ec96083..4b1def2 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -1,3 +1,20 @@ +/* +Copyright 2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package config_test import ( @@ -116,6 +133,7 @@ ecc-curve: P-521 data, err := yaml.Marshal(item.pkInfo) if item.expectErr { require.Error(t, err) + return } @@ -375,6 +393,7 @@ ext-key-usages: err := yaml.Unmarshal([]byte(item.yaml), &entry) if item.expectErr { require.Error(t, err) + return } @@ -544,6 +563,7 @@ func loadSignerConfig(t *testing.T) *config.SignerConfig { require.NoError(t, err) require.NotNil(t, sc) + return sc } @@ -726,7 +746,8 @@ url-patterns: crl: http://crl.example.org/%s.crl issuer: http://issuer.example.org/%s.crt `, - errMsg: "invalid Settings validity of root CA certificates must be equal or greater than those if intermediary CA certificates", + errMsg: "invalid Settings validity of root CA certificates must be equal or greater than " + + "those of intermediary CA certificates", }, "no OCSP pattern": { yaml: ` diff --git a/pkg/hsm/context.go b/pkg/hsm/context.go index 5731488..83aa903 100644 --- a/pkg/hsm/context.go +++ b/pkg/hsm/context.go @@ -1,7 +1,25 @@ +/* +Copyright 2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package hsm import ( "context" + "errors" "github.com/ThalesIgnite/crypto11" @@ -78,7 +96,10 @@ func IsVerbose(ctx context.Context) bool { } func GetP11Context(ctx context.Context, entry *config.CaCertificateEntry) (*crypto11.Context, error) { - contexts := ctx.Value(ctxP11Contexts).(map[string]*crypto11.Context) + contexts, ok := ctx.Value(ctxP11Contexts).(map[string]*crypto11.Context) + if !ok { + return nil, errors.New("type assertion failed") + } if p11Context, ok := contexts[entry.Storage]; ok { return p11Context, nil diff --git a/pkg/hsm/hsm.go b/pkg/hsm/hsm.go index 57fc7c8..3ea050e 100644 --- a/pkg/hsm/hsm.go +++ b/pkg/hsm/hsm.go @@ -1,3 +1,21 @@ +/* +Copyright 2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package hsm handles hardware security modules. package hsm import ( @@ -40,7 +58,7 @@ func GetRootCACertificate(ctx context.Context, label string) (*x509.Certificate, caCert, err := sc.GetCADefinition(label) if err != nil { - return nil, err + return nil, fmt.Errorf("could not get CA definition for label %s: %w", label, err) } if !caCert.IsRoot() { @@ -122,7 +140,7 @@ func GetIntermediaryCACertificate(ctx context.Context, certLabel string) (*x509. caCert, err := sc.GetCADefinition(certLabel) if err != nil { - return nil, err + return nil, fmt.Errorf("could not get CA definition for label %s: %w", certLabel, err) } if caCert.IsRoot() { @@ -200,10 +218,15 @@ func GetIntermediaryCACertificate(ctx context.Context, certLabel string) (*x509. return certificate, nil } -func generateIntermediaryCACertificate(config *config.SignerConfig, certLabel string, publicKey crypto.PublicKey, template *x509.Certificate) (*x509.Certificate, error) { +func generateIntermediaryCACertificate( + config *config.SignerConfig, + certLabel string, + publicKey crypto.PublicKey, + template *x509.Certificate, +) (*x509.Certificate, error) { parent, err := config.GetParentCA(certLabel) if err != nil { - return nil, err + return nil, fmt.Errorf("could not get parent CA for label %s: %w", certLabel, err) } serial, err := randomSerialNumber() @@ -250,12 +273,12 @@ func generateIntermediaryCACertificate(config *config.SignerConfig, certLabel st } func addCertificate(p11Context *crypto11.Context, label string, certificate *x509.Certificate) error { - objectId, err := randomObjectId() + objectID, err := randomObjectID() if err != nil { return err } - err = p11Context.ImportCertificateWithLabel(objectId, []byte(label), certificate) + err = p11Context.ImportCertificateWithLabel(objectID, []byte(label), certificate) if err != nil { return fmt.Errorf("could not import certificate into token: %w", err) } @@ -265,9 +288,10 @@ func addCertificate(p11Context *crypto11.Context, label string, certificate *x50 func getKeyPair(ctx context.Context, label string, keyInfo *config.PrivateKeyInfo) (crypto.Signer, error) { sc := GetSignerConfig(ctx) + cert, err := sc.GetCADefinition(label) if err != nil { - return nil, err + return nil, fmt.Errorf("could not get CA definition for label %s: %w", label, err) } if cert.KeyPair != nil { @@ -296,48 +320,90 @@ func getKeyPair(ctx context.Context, label string, keyInfo *config.PrivateKeyInf case x509.RSA: keyPair, err = generateRSAKeyPair(p11Context, label, keyInfo) if err != nil { - return nil, fmt.Errorf("could not generate RSA key pair: %w", err) + return nil, err } case x509.ECDSA: keyPair, err = generateECDSAKeyPair(p11Context, label, keyInfo) if err != nil { - return nil, fmt.Errorf("could not generate ECDSA key pair: %w", err) + return nil, err } default: - return nil, fmt.Errorf("could not generate private key with label %s with unsupported key algorithm %s", label, keyInfo.Algorithm) + return nil, fmt.Errorf( + "could not generate private key with label %s with unsupported key algorithm %s", + label, + keyInfo.Algorithm, + ) } return keyPair, nil } -func generateECDSAKeyPair(p11Context *crypto11.Context, label string, keyInfo *config.PrivateKeyInfo) (crypto11.Signer, error) { - newObjectId, err := randomObjectId() +func generateECDSAKeyPair( + p11Context *crypto11.Context, + label string, + keyInfo *config.PrivateKeyInfo, +) (crypto11.Signer, error) { + var ( + err error + newObjectID []byte + signer crypto11.Signer + ) + + newObjectID, err = randomObjectID() if err != nil { return nil, err } - return p11Context.GenerateECDSAKeyPairWithLabel(newObjectId, []byte(label), keyInfo.EccCurve) + signer, err = p11Context.GenerateECDSAKeyPairWithLabel(newObjectID, []byte(label), keyInfo.EccCurve) + if err != nil { + return nil, fmt.Errorf("could not generate ECDSA key pair: %w", err) + } + + return signer, nil } -func generateRSAKeyPair(p11Context *crypto11.Context, label string, keyInfo *config.PrivateKeyInfo) (crypto11.Signer, error) { - newObjectId, err := randomObjectId() +func generateRSAKeyPair( + p11Context *crypto11.Context, + label string, + keyInfo *config.PrivateKeyInfo, +) (crypto11.Signer, error) { + var ( + err error + newObjectID []byte + signer crypto11.Signer + ) + + newObjectID, err = randomObjectID() if err != nil { return nil, err } - return p11Context.GenerateRSAKeyPairWithLabel(newObjectId, []byte(label), keyInfo.RSABits) + signer, err = p11Context.GenerateRSAKeyPairWithLabel(newObjectID, []byte(label), keyInfo.RSABits) + if err != nil { + return nil, fmt.Errorf("could not generate RSA key pair: %w", err) + } + + return signer, nil } -func randomObjectId() ([]byte, error) { - result := make([]byte, 20) +func randomObjectID() ([]byte, error) { + const objectIDSize = 20 + + result := make([]byte, objectIDSize) + _, err := rand.Read(result) if err != nil { return nil, fmt.Errorf("could not create new random object id: %w", err) } + return result, nil } -func generateRootCACertificate(certFile string, keyPair crypto.Signer, template *x509.Certificate) (*x509.Certificate, error) { +func generateRootCACertificate( + certFile string, + keyPair crypto.Signer, + template *x509.Certificate, +) (*x509.Certificate, error) { serial, err := randomSerialNumber() if err != nil { return nil, err @@ -414,6 +480,7 @@ func certificateMatches(certificate *x509.Certificate, key crypto.Signer) bool { certificate.PublicKey, key.Public(), ) + return false } @@ -423,6 +490,7 @@ func loadCertificate(certFile string) (*x509.Certificate, error) { if errors.Is(err, syscall.ENOENT) { return nil, nil } + return nil, fmt.Errorf("could not get info for %s: %w", certFile, err) } @@ -453,10 +521,10 @@ func loadCertificate(certFile string) (*x509.Certificate, error) { } func randomSerialNumber() (*big.Int, error) { - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { return nil, fmt.Errorf("could not generate serial number: %w", err) } + return serialNumber, nil } diff --git a/pkg/hsm/setup.go b/pkg/hsm/setup.go index 4d9fc01..5d63e7e 100644 --- a/pkg/hsm/setup.go +++ b/pkg/hsm/setup.go @@ -1,3 +1,20 @@ +/* +Copyright 2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package hsm import ( diff --git a/pkg/hsm/storage.go b/pkg/hsm/storage.go index f2164e5..1605819 100644 --- a/pkg/hsm/storage.go +++ b/pkg/hsm/storage.go @@ -1,3 +1,20 @@ +/* +Copyright 2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package hsm import ( @@ -39,13 +56,15 @@ func prepareCrypto11Context(ctx context.Context, label string) (*crypto11.Contex p11Context, err = crypto11.Configure(p11Config) if err != nil { - return nil, fmt.Errorf("could not configure PKCS#11 library: %v", err) + return nil, fmt.Errorf("could not configure PKCS#11 library: %w", err) } return p11Context, nil } func getPin(p11Config *crypto11.Config) (string, error) { + var err error + tokenPinEnv := strings.ReplaceAll(p11Config.TokenLabel, "-", "_") tokenPinEnv = strings.ReplaceAll(tokenPinEnv, " ", "_") tokenPinEnv = strings.ToUpper(tokenPinEnv) @@ -59,14 +78,20 @@ func getPin(p11Config *crypto11.Config) (string, error) { return "", errors.New("stdin is not a terminal") } - fmt.Printf("Enter PIN for token %s: ", p11Config.TokenLabel) + _, err = fmt.Fprintf(os.Stdout, "Enter PIN for token %s: ", p11Config.TokenLabel) + if err != nil { + return "", fmt.Errorf("could not write PIN prompt: %w", err) + } bytePin, err := term.ReadPassword(syscall.Stdin) if err != nil { - return "", errors.New("could not read PIN") + return "", fmt.Errorf("could not read PIN: %w", err) } - fmt.Println() + _, err = fmt.Fprintln(os.Stdout) + if err != nil { + return "", fmt.Errorf("could not write to stdout: %w", err) + } pin = string(bytePin) } diff --git a/pkg/openpgp/signing/repository.go b/pkg/openpgp/signing/repository.go index 23021ae..fc37e32 100644 --- a/pkg/openpgp/signing/repository.go +++ b/pkg/openpgp/signing/repository.go @@ -1,3 +1,20 @@ +/* +Copyright 2021-2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package signing type Repository interface { diff --git a/pkg/openpgp/signing/signing.go b/pkg/openpgp/signing/signing.go index 7ac9b7c..6ae40cb 100644 --- a/pkg/openpgp/signing/signing.go +++ b/pkg/openpgp/signing/signing.go @@ -1,3 +1,20 @@ +/* +Copyright 2021-2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package signing type OpenPGPSigning struct{} diff --git a/pkg/x509/openssl/repository.go b/pkg/x509/openssl/repository.go index d3b31ca..45788d4 100644 --- a/pkg/x509/openssl/repository.go +++ b/pkg/x509/openssl/repository.go @@ -1,3 +1,20 @@ +/* +Copyright 2021-2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package openssl import ( @@ -27,6 +44,15 @@ const ( certificateExpired indexStatus = "E" ) +const ( + posStatus int = iota + posExpiry + posRevocation + posSerial + posFilename + posSubject +) + // An indexEntry represents a line in an openssl ca compatible index.txt file // a format specification is available at https://pki-tutorial.readthedocs.io/en/latest/cadb.html type indexEntry struct { @@ -134,6 +160,7 @@ func (r *Repository) StoreRevocation(revoked *pkix.RevokedCertificate) error { entry.markRevoked(revoked.RevocationTime, reason) err = r.writeIndex() + return err } @@ -189,6 +216,7 @@ func (r *Repository) RevokedCertificates() ([]pkix.RevokedCertificate, error) { } result := make([]pkix.RevokedCertificate, 0) + for _, entry := range r.entries { if entry.revokedAt != nil { result = append(result, pkix.RevokedCertificate{ @@ -203,7 +231,9 @@ func (r *Repository) RevokedCertificates() ([]pkix.RevokedCertificate, error) { } func (r *Repository) loadIndex() error { - entries := make([]indexEntry, 0, 100) + const reservedEntries = 100 // use 100 as base capacity to avoid unnecessary array resizing + + entries := make([]indexEntry, 0, reservedEntries) f, err := os.Open(r.indexFileName) if err != nil && !os.IsNotExist(err) { @@ -224,10 +254,12 @@ func (r *Repository) loadIndex() error { if err != nil { return err } + entries = append(entries, *indexEntry) } + if err := indexScanner.Err(); err != nil { - return err + return fmt.Errorf("could not scan index file: %w", err) } r.entries = entries @@ -240,6 +272,7 @@ func (r *Repository) writeIndex() error { if err != nil { return fmt.Errorf("could not create index file %s: %w", r.indexFileName, err) } + defer func(f *os.File) { _ = f.Close() }(f) @@ -251,6 +284,7 @@ func (r *Repository) writeIndex() error { if err != nil { return fmt.Errorf("could not write entry for serial %s: %w", entry.serialNumber, err) } + if i < len(r.entries)-1 { _, err = w.WriteString("\n") if err != nil { @@ -258,6 +292,7 @@ func (r *Repository) writeIndex() error { } } } + err = w.Flush() if err != nil { return fmt.Errorf("could not write to %s: %w", r.indexFileName, err) @@ -278,11 +313,11 @@ func (r *Repository) addIndexEntry(ie *indexEntry) error { } func (r *Repository) newIndexEntryFromLine(text string) (*indexEntry, error) { + const expectedFieldNumber = 6 + var err error - fields := strings.Split(text, "\t") - - const expectedFieldNumber = 6 + fields := strings.SplitN(text, "\t", expectedFieldNumber) if len(fields) != expectedFieldNumber { return nil, fmt.Errorf( "unexpected number of fields %d instead of %d", @@ -291,59 +326,63 @@ func (r *Repository) newIndexEntryFromLine(text string) (*indexEntry, error) { ) } - expirationParsed, err := time.Parse(TimeSpec, fields[1]) + expirationParsed, err := time.Parse(TimeSpec, fields[posExpiry]) if err != nil { - return nil, err + return nil, fmt.Errorf("could not parse expiration time %s: %w", fields[posExpiry], err) } - var revocationTimeParsed time.Time - var revocationReason revoking.CRLReason + var ( + revocationTimeParsed time.Time + revocationReason revoking.CRLReason + ) - if fields[2] != "" { - var timeString string - if strings.Contains(fields[2], ",") { - parts := strings.SplitN(fields[2], ",", 2) - timeString = parts[0] - revocationReason = revoking.ParseReason(parts[1]) - } else { - timeString = fields[2] + if fields[posRevocation] != "" { + timeString, reasonText, found := strings.Cut(fields[revocationReason], ",") + if found { + revocationReason = revoking.ParseReason(reasonText) } + revocationTimeParsed, err = time.Parse(TimeSpec, timeString) if err != nil { - return nil, err + return nil, fmt.Errorf("could not parse revocation time %s: %w", timeString, err) } } serialParsed := new(big.Int) - if _, ok := serialParsed.SetString(fields[3], 16); !ok { + if _, ok := serialParsed.SetString(fields[posSerial], 16); !ok { return nil, fmt.Errorf("could not parse serial number %s", fields[3]) } fileNameParsed := "unknown" - if fields[4] != "" { - _, err = os.Stat(path.Join(path.Dir(r.indexFileName), fields[4])) + + if fields[posFilename] != "" { + certificateFile := path.Join(path.Dir(r.indexFileName), fields[posFilename]) + + _, err = os.Stat(certificateFile) if err != nil && !os.IsNotExist(err) { - return nil, err + return nil, fmt.Errorf("could not check certificate file %s: %w", certificateFile, err) } - fileNameParsed = fields[4] + + fileNameParsed = fields[posFilename] } return &indexEntry{ - statusFlag: indexStatus(fields[0]), + statusFlag: indexStatus(fields[posStatus]), expiresAt: expirationParsed, revokedAt: &revocationTimeParsed, revocationReason: revocationReason, serialNumber: serialParsed, fileName: fileNameParsed, - certificateSubjectDN: fields[5], + certificateSubjectDN: fields[posSubject], }, nil } func NewFileRepository(baseDirectory string) (*Repository, error) { err := os.Chdir(baseDirectory) if err != nil { - return nil, err + return nil, fmt.Errorf("could not change to base directory %s: %w", baseDirectory, err) } + return &Repository{ indexFileName: path.Join(baseDirectory, "index.txt"), lock: &sync.Mutex{}, diff --git a/pkg/x509/openssl/repository_test.go b/pkg/x509/openssl/repository_test.go index cf6c7ba..4378385 100644 --- a/pkg/x509/openssl/repository_test.go +++ b/pkg/x509/openssl/repository_test.go @@ -1,3 +1,20 @@ +/* +Copyright 2021-2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package openssl_test import ( @@ -11,10 +28,11 @@ import ( "testing" "time" - "git.cacert.org/cacert-gosigner/pkg/x509/openssl" - "git.cacert.org/cacert-gosigner/pkg/x509/revoking" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "git.cacert.org/cacert-gosigner/pkg/x509/openssl" + "git.cacert.org/cacert-gosigner/pkg/x509/revoking" ) func TestStoreRevocation(t *testing.T) { diff --git a/pkg/x509/revoking/repository.go b/pkg/x509/revoking/repository.go index 637b676..54a9f40 100644 --- a/pkg/x509/revoking/repository.go +++ b/pkg/x509/revoking/repository.go @@ -1,3 +1,20 @@ +/* +Copyright 2021-2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package revoking import ( diff --git a/pkg/x509/revoking/revoking.go b/pkg/x509/revoking/revoking.go index de3a394..fbbb99e 100644 --- a/pkg/x509/revoking/revoking.go +++ b/pkg/x509/revoking/revoking.go @@ -1,3 +1,21 @@ +/* +Copyright 2021-2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package revoking takes care of handling certificate revocation requests. package revoking import ( @@ -85,12 +103,6 @@ type RevokeCertificate struct { reason CRLReason } -type CertificateRevoked struct { - serialNumber *big.Int - revocationTime time.Time - reason string -} - type CRLInformation struct { CRL []byte // DER encoded CRL } @@ -103,7 +115,7 @@ func (r *X509Revoking) Revoke(revokeCertificate *RevokeCertificate) (*pkix.Revok } if err := r.repository.StoreRevocation(revoked); err != nil { - return nil, err + return nil, fmt.Errorf("could not store revocation %w", err) } return revoked, nil diff --git a/pkg/x509/revoking/revoking_test.go b/pkg/x509/revoking/revoking_test.go index df3eba3..5f73c66 100644 --- a/pkg/x509/revoking/revoking_test.go +++ b/pkg/x509/revoking/revoking_test.go @@ -1,3 +1,20 @@ +/* +Copyright 2021-2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package revoking import ( @@ -20,8 +37,10 @@ func (t *testRepo) RevokedCertificates() ([]pkix.RevokedCertificate, error) { result := make([]pkix.RevokedCertificate, len(t.revoked)) for i, s := range t.revoked { + serialNumber := s + result[i] = pkix.RevokedCertificate{ - SerialNumber: &s, + SerialNumber: &serialNumber, RevocationTime: time.Now(), } } @@ -31,15 +50,18 @@ func (t *testRepo) RevokedCertificates() ([]pkix.RevokedCertificate, error) { func (t *testRepo) StoreRevocation(revoked *pkix.RevokedCertificate) error { t.revoked = append(t.revoked, *revoked.SerialNumber) + return nil } func randomSerial(t *testing.T) *big.Int { t.Helper() + serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { t.Fatalf("could not generate random serial number: %v", err) } + return serial } @@ -50,12 +72,14 @@ func TestRevoking(t *testing.T) { if err != nil { t.Fatalf("could not generate key pair: %v", err) } + caTemplate := &x509.Certificate{Subject: pkix.Name{CommonName: "Test CA"}, SerialNumber: randomSerial(t)} certificateBytes, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, caKey.Public(), caKey) if err != nil { t.Fatalf("could not self-sign CA certificate: %v", err) } + caCertificate, err := x509.ParseCertificate(certificateBytes) if err != nil { t.Fatalf("could not create test CA certificate: %v", err) diff --git a/pkg/x509/signing/repository.go b/pkg/x509/signing/repository.go index c294f97..78d4054 100644 --- a/pkg/x509/signing/repository.go +++ b/pkg/x509/signing/repository.go @@ -1,3 +1,20 @@ +/* +Copyright 2021-2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package signing import "crypto/x509" diff --git a/pkg/x509/signing/signer.go b/pkg/x509/signing/signer.go index 0adfe94..bcea70a 100644 --- a/pkg/x509/signing/signer.go +++ b/pkg/x509/signing/signer.go @@ -1,3 +1,21 @@ +/* +Copyright 2021-2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package signing takes care of X.509 certificate signing. package signing import ( @@ -10,7 +28,7 @@ type SignerRequest struct { CSR *x509.CertificateRequest SubjectDN pkix.Name Emails []string - DnsNames []string + DNSNames []string Duration time.Duration SignatureAlgorithm x509.SignatureAlgorithm } @@ -26,7 +44,7 @@ func NewSignerRequest( CSR: csr, SubjectDN: subjectDN, Emails: emails, - DnsNames: dnsNames, + DNSNames: dnsNames, Duration: duration, SignatureAlgorithm: signatureAlgorithm, } diff --git a/pkg/x509/signing/signing.go b/pkg/x509/signing/signing.go index 3ca385d..dc9d4b4 100644 --- a/pkg/x509/signing/signing.go +++ b/pkg/x509/signing/signing.go @@ -1,3 +1,20 @@ +/* +Copyright 2021-2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package signing import ( @@ -16,8 +33,6 @@ func NewX509Signing(signer Signer, repo Repository) *X509Signing { return &X509Signing{signer: signer, repo: repo} } -type CertificatePolicyId int - type RequestSignature struct { rawCSRData []byte subjectCommonName string @@ -70,12 +85,12 @@ func (x *X509Signing) Sign(signingRequest *RequestSignature) (*CertificateSigned ), ) if err != nil { - return nil, err + return nil, fmt.Errorf("could not sign certificate: %w", err) } err = x.repo.StoreCertificate(certificateFromSigner.Certificate) if err != nil { - return nil, err + return nil, fmt.Errorf("could not store certificate: %w", err) } return &CertificateSigned{certificate: certificateFromSigner.Certificate}, nil diff --git a/pkg/x509/signing/signing_test.go b/pkg/x509/signing/signing_test.go index c5f622e..cc8b3b7 100644 --- a/pkg/x509/signing/signing_test.go +++ b/pkg/x509/signing/signing_test.go @@ -1,3 +1,20 @@ +/* +Copyright 2021-2022 CAcert Inc. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package signing_test import ( @@ -6,12 +23,14 @@ import ( "crypto/rsa" "crypto/x509" "crypto/x509/pkix" + "fmt" "math/big" "testing" "time" - "git.cacert.org/cacert-gosigner/pkg/x509/signing" "github.com/stretchr/testify/assert" + + "git.cacert.org/cacert-gosigner/pkg/x509/signing" ) type testRepo struct { @@ -20,6 +39,7 @@ type testRepo struct { func (r *testRepo) StoreCertificate(certificate *x509.Certificate) error { r.certs[certificate.SerialNumber.Text(16)] = *certificate + return nil } @@ -35,10 +55,12 @@ func newTestSignerResponse(certificate *x509.Certificate) *signing.SignerRespons func randomSerial(t *testing.T) *big.Int { t.Helper() + serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { t.Fatalf("could not generate random serial number: %v", err) } + return serial } @@ -57,12 +79,12 @@ func (s *testSigner) SignCertificate(request *signing.SignerRequest) (*signing.S certBytes, err := x509.CreateCertificate(rand.Reader, template, s.certificate, request.CSR.PublicKey, s.key) if err != nil { - return nil, err + return nil, fmt.Errorf("could not sign certificate: %w", err) } certificate, err := x509.ParseCertificate(certBytes) if err != nil { - return nil, err + return nil, fmt.Errorf("could not parse certificate: %w", err) } return newTestSignerResponse(certificate), nil @@ -79,12 +101,21 @@ func TestSigning(t *testing.T) { } csrTemplate := &x509.CertificateRequest{PublicKey: csrKey.Public()} + csrBytes, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, csrKey) if err != nil { t.Error(err) } - testRequest := signing.NewRequestSignature(csrBytes, "Test Subject", []string{"test@example.org"}, nil, 365*24*time.Hour, x509.SHA384WithRSA) + testRequest := signing.NewRequestSignature( + csrBytes, + "Test Subject", + []string{"test@example.org"}, + nil, + 365*24*time.Hour, + x509.SHA384WithRSA, + ) + signed, err := s.Sign(testRequest) if err != nil { t.Error(err) @@ -98,19 +129,23 @@ func TestSigning(t *testing.T) { func newTestSigner(t *testing.T) *testSigner { t.Helper() + caKey, err := rsa.GenerateKey(rand.Reader, 3072) if err != nil { t.Fatalf("could not generate key pair: %v", err) } + caTemplate := &x509.Certificate{Subject: pkix.Name{CommonName: "Test CA"}, SerialNumber: randomSerial(t)} certificateBytes, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, caKey.Public(), caKey) if err != nil { t.Fatalf("could not self-sign CA certificate: %v", err) } + caCertificate, err := x509.ParseCertificate(certificateBytes) if err != nil { t.Fatalf("could not create test CA certificate: %v", err) } + return &testSigner{key: caKey, certificate: caCertificate, t: t} }