-
Notifications
You must be signed in to change notification settings - Fork 0
/
certificates.go
179 lines (154 loc) · 4.83 KB
/
certificates.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package xmldsig
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"os"
"strings"
"software.sslmate.com/src/go-pkcs12"
)
// ErrNotFound is returned when a matching certificate was not found.
var ErrNotFound = errors.New("not found")
// Certificate stores information about a signing Certificate
// which can be used to sign a facturae XML
type Certificate struct {
privateKey *rsa.PrivateKey
certificate *x509.Certificate
CaChain []*x509.Certificate
issuer *pkix.RDNSequence
}
// PrivateKeyInfo contains info about modulus and exponent of the key
type PrivateKeyInfo struct {
Modulus string
Exponent string
}
// LoadCertificate creates a new Certificate instance from the info
// obtained from pkcs12 formated data stream
func LoadCertificate(path, password string) (*Certificate, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("loading certificate: %w", err)
}
privateKey, certificate, caChain, err := pkcs12.DecodeChain(data, password)
if err != nil {
return nil, err
}
issuer := &pkix.RDNSequence{}
_, err = asn1.Unmarshal(certificate.RawIssuer, issuer)
if err != nil {
return nil, err
}
rsaPrivateKey := privateKey.(*rsa.PrivateKey)
return &Certificate{
privateKey: rsaPrivateKey,
certificate: certificate,
CaChain: caChain,
issuer: issuer,
}, nil
}
// Sign will first create a hash of the data passed and then
// create a string (base64) representation of the signature obtained
// using the private key of the certificate
func (cert *Certificate) Sign(data string) (string, error) {
hash := makeHash(data)
signature, signingErr := rsa.SignPKCS1v15(rand.Reader, cert.privateKey, crypto.SHA256, hash)
if signingErr != nil {
return "", signingErr
}
return base64.StdEncoding.EncodeToString(signature), nil
}
func makeHash(data string) []byte {
hasher := crypto.SHA256.New()
hasher.Write([]byte(data))
return hasher.Sum(nil)
}
// Fingerprint will return the SHA512 hash of the public key
func (cert *Certificate) Fingerprint() string {
hasher := crypto.SHA512.New()
hasher.Write(cert.certificate.Raw)
return base64.StdEncoding.EncodeToString(hasher.Sum(nil))
}
// NakedPEM will return the public certificate encoded in base64 PEM
// (without markers like "-----BEGIN CERTIFICATE-----")
func (cert *Certificate) NakedPEM() string {
return NakedPEM(cert.certificate)
}
// PEM provides the PEM representation of the certificate.
func (cert *Certificate) PEM() []byte {
return PEMCertificate(cert.certificate)
}
// PrivateKey provides the private key in PEM format.
func (cert *Certificate) PrivateKey() []byte {
return PEMPrivateRSAKey(cert.privateKey)
}
// NakedPEM converts a x509 formated certificate to the PEM format without
// the headers, useful for including in the XML document.
func NakedPEM(cert *x509.Certificate) string {
replacer := strings.NewReplacer(
"-----BEGIN CERTIFICATE-----", "",
"-----END CERTIFICATE-----", "",
"\n", "")
pem := string(PEMCertificate(cert))
return replacer.Replace(pem)
}
// PEMCertificate provides the complete PEM version of the certificate.
func PEMCertificate(cert *x509.Certificate) []byte {
pemBlock := pem.Block{
Type: "CERTIFICATE",
Headers: map[string]string{},
Bytes: cert.Raw,
}
return pem.EncodeToMemory(&pemBlock)
}
// PEMPrivateRSAKey issues a PEM string with the RSA Key.
func PEMPrivateRSAKey(key *rsa.PrivateKey) []byte {
pb := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
}
return pem.EncodeToMemory(pb)
}
// Issuer returns a description of the certificate issuer
func (cert *Certificate) Issuer() string {
return cert.issuer.String()
}
// SerialNumber returns the serial number of the certificate
func (cert *Certificate) SerialNumber() string {
return cert.certificate.SerialNumber.String()
}
// PrivateKeyInfo is the RSA private key info
func (cert *Certificate) PrivateKeyInfo() *PrivateKeyInfo {
exponentBytes := make([]byte, 3)
exponentBytes[0] = byte(cert.privateKey.E >> 16)
exponentBytes[1] = byte(cert.privateKey.E >> 8)
exponentBytes[2] = byte(cert.privateKey.E)
return &PrivateKeyInfo{
Modulus: base64.StdEncoding.EncodeToString(cert.privateKey.N.Bytes()),
Exponent: base64.StdEncoding.EncodeToString(exponentBytes),
}
}
// TLSAuthConfig prepares TLS authentication connection details ready to use
// with HTTP servers that require them in addition to the signatures of the
// XML-DSig signed payload.
func (cert *Certificate) TLSAuthConfig() (*tls.Config, error) {
pair, err := tls.X509KeyPair(cert.PEM(), cert.PrivateKey())
if err != nil {
return nil, err
}
rootCAs := x509.NewCertPool()
for _, c := range cert.CaChain {
rootCAs.AddCert(c)
}
return &tls.Config{
RootCAs: rootCAs,
Certificates: []tls.Certificate{pair},
}, nil
}