mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-22 05:40:50 +01:00
feat: migrated resources from external repo to remove external dependency
This commit is contained in:
parent
128b2bd32e
commit
efd8aa93b6
4 changed files with 576 additions and 4 deletions
3
go.mod
3
go.mod
|
@ -7,7 +7,6 @@ module github.com/wneessen/go-mail
|
|||
go 1.16
|
||||
|
||||
require (
|
||||
go.mozilla.org/pkcs7 v0.9.0
|
||||
golang.org/x/crypto v0.28.0
|
||||
golang.org/x/text v0.19.0
|
||||
)
|
||||
)
|
2
go.sum
2
go.sum
|
@ -20,8 +20,6 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI=
|
||||
go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
|
386
pkcs7.go
Normal file
386
pkcs7.go
Normal file
|
@ -0,0 +1,386 @@
|
|||
// SPDX-FileCopyrightText: 2022-2023 The go-mail Authors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
_ "crypto/sha1" // for crypto.SHA1
|
||||
)
|
||||
|
||||
// PKCS7 Represents a PKCS7 structure
|
||||
type PKCS7 struct {
|
||||
Content []byte
|
||||
Certificates []*x509.Certificate
|
||||
CRLs []pkix.CertificateList
|
||||
Signers []signerInfo
|
||||
raw interface{}
|
||||
}
|
||||
|
||||
type contentInfo struct {
|
||||
ContentType asn1.ObjectIdentifier
|
||||
Content asn1.RawValue `asn1:"explicit,optional,tag:0"`
|
||||
}
|
||||
|
||||
var (
|
||||
oidData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1}
|
||||
oidSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2}
|
||||
oidAttributeContentType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3}
|
||||
oidAttributeMessageDigest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4}
|
||||
oidAttributeSigningTime = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 5}
|
||||
)
|
||||
|
||||
type signedData struct {
|
||||
Version int `asn1:"default:1"`
|
||||
DigestAlgorithmIdentifiers []pkix.AlgorithmIdentifier `asn1:"set"`
|
||||
ContentInfo contentInfo
|
||||
Certificates rawCertificates `asn1:"optional,tag:0"`
|
||||
CRLs []pkix.CertificateList `asn1:"optional,tag:1"`
|
||||
SignerInfos []signerInfo `asn1:"set"`
|
||||
}
|
||||
|
||||
type rawCertificates struct {
|
||||
Raw asn1.RawContent
|
||||
}
|
||||
|
||||
type attribute struct {
|
||||
Type asn1.ObjectIdentifier
|
||||
Value asn1.RawValue `asn1:"set"`
|
||||
}
|
||||
|
||||
type issuerAndSerial struct {
|
||||
IssuerName asn1.RawValue
|
||||
SerialNumber *big.Int
|
||||
}
|
||||
|
||||
// MessageDigestMismatchError is returned when the signer data digest does not
|
||||
// match the computed digest for the contained content
|
||||
type MessageDigestMismatchError struct {
|
||||
ExpectedDigest []byte
|
||||
ActualDigest []byte
|
||||
}
|
||||
|
||||
func (err *MessageDigestMismatchError) Error() string {
|
||||
return fmt.Sprintf("pkcs7: Message digest mismatch\n\tExpected: %X\n\tActual : %X", err.ExpectedDigest, err.ActualDigest)
|
||||
}
|
||||
|
||||
type signerInfo struct {
|
||||
Version int `asn1:"default:1"`
|
||||
IssuerAndSerialNumber issuerAndSerial
|
||||
DigestAlgorithm pkix.AlgorithmIdentifier
|
||||
AuthenticatedAttributes []attribute `asn1:"optional,tag:0"`
|
||||
DigestEncryptionAlgorithm pkix.AlgorithmIdentifier
|
||||
EncryptedDigest []byte
|
||||
UnauthenticatedAttributes []attribute `asn1:"optional,tag:1"`
|
||||
}
|
||||
|
||||
func (raw rawCertificates) Parse() ([]*x509.Certificate, error) {
|
||||
if len(raw.Raw) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var val asn1.RawValue
|
||||
if _, err := asn1.Unmarshal(raw.Raw, &val); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return x509.ParseCertificates(val.Bytes)
|
||||
}
|
||||
|
||||
func marshalAttributes(attrs []attribute) ([]byte, error) {
|
||||
encodedAttributes, err := asn1.Marshal(struct {
|
||||
A []attribute `asn1:"set"`
|
||||
}{A: attrs})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Remove the leading sequence octets
|
||||
var raw asn1.RawValue
|
||||
asn1.Unmarshal(encodedAttributes, &raw)
|
||||
return raw.Bytes, nil
|
||||
}
|
||||
|
||||
var (
|
||||
oidDigestAlgorithmSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26}
|
||||
oidSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5}
|
||||
)
|
||||
|
||||
func getCertFromCertsByIssuerAndSerial(certs []*x509.Certificate, ias issuerAndSerial) *x509.Certificate {
|
||||
for _, cert := range certs {
|
||||
if isCertMatchForIssuerAndSerial(cert, ias) {
|
||||
return cert
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOnlySigner returns an x509.Certificate for the first signer of the signed
|
||||
// data payload. If there are more or less than one signer, nil is returned
|
||||
func (p7 *PKCS7) GetOnlySigner() *x509.Certificate {
|
||||
if len(p7.Signers) != 1 {
|
||||
return nil
|
||||
}
|
||||
signer := p7.Signers[0]
|
||||
return getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber)
|
||||
}
|
||||
|
||||
// ErrUnsupportedAlgorithm tells you when our quick dev assumptions have failed
|
||||
var ErrUnsupportedAlgorithm = errors.New("pkcs7: cannot decrypt data: only RSA, DES, DES-EDE3, AES-256-CBC and AES-128-GCM supported")
|
||||
|
||||
func isCertMatchForIssuerAndSerial(cert *x509.Certificate, ias issuerAndSerial) bool {
|
||||
return cert.SerialNumber.Cmp(ias.SerialNumber) == 0 && bytes.Compare(cert.RawIssuer, ias.IssuerName.FullBytes) == 0
|
||||
}
|
||||
|
||||
func unmarshalAttribute(attrs []attribute, attributeType asn1.ObjectIdentifier, out interface{}) error {
|
||||
for _, attr := range attrs {
|
||||
if attr.Type.Equal(attributeType) {
|
||||
_, err := asn1.Unmarshal(attr.Value.Bytes, out)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return errors.New("pkcs7: attribute type not in attributes")
|
||||
}
|
||||
|
||||
// UnmarshalSignedAttribute decodes a single attribute from the signer info
|
||||
func (p7 *PKCS7) UnmarshalSignedAttribute(attributeType asn1.ObjectIdentifier, out interface{}) error {
|
||||
sd, ok := p7.raw.(signedData)
|
||||
if !ok {
|
||||
return errors.New("pkcs7: payload is not signedData content")
|
||||
}
|
||||
if len(sd.SignerInfos) < 1 {
|
||||
return errors.New("pkcs7: payload has no signers")
|
||||
}
|
||||
attributes := sd.SignerInfos[0].AuthenticatedAttributes
|
||||
return unmarshalAttribute(attributes, attributeType, out)
|
||||
}
|
||||
|
||||
// SignedData is an opaque data structure for creating signed data payloads
|
||||
type SignedData struct {
|
||||
sd signedData
|
||||
certs []*x509.Certificate
|
||||
messageDigest []byte
|
||||
}
|
||||
|
||||
// Attribute represents a key value pair attribute. Value must be marshalable byte
|
||||
// `encoding/asn1`
|
||||
type Attribute struct {
|
||||
Type asn1.ObjectIdentifier
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// SignerInfoConfig are optional values to include when adding a signer
|
||||
type SignerInfoConfig struct {
|
||||
ExtraSignedAttributes []Attribute
|
||||
}
|
||||
|
||||
// newSignedData initializes a SignedData with content
|
||||
func newSignedData(data []byte) (*SignedData, error) {
|
||||
content, err := asn1.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ci := contentInfo{
|
||||
ContentType: oidData,
|
||||
Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true},
|
||||
}
|
||||
digAlg := pkix.AlgorithmIdentifier{
|
||||
Algorithm: oidDigestAlgorithmSHA1,
|
||||
}
|
||||
h := crypto.SHA1.New()
|
||||
h.Write(data)
|
||||
md := h.Sum(nil)
|
||||
sd := signedData{
|
||||
ContentInfo: ci,
|
||||
Version: 1,
|
||||
DigestAlgorithmIdentifiers: []pkix.AlgorithmIdentifier{digAlg},
|
||||
}
|
||||
return &SignedData{sd: sd, messageDigest: md}, nil
|
||||
}
|
||||
|
||||
type attributes struct {
|
||||
types []asn1.ObjectIdentifier
|
||||
values []interface{}
|
||||
}
|
||||
|
||||
// Add adds the attribute, maintaining insertion order
|
||||
func (attrs *attributes) Add(attrType asn1.ObjectIdentifier, value interface{}) {
|
||||
attrs.types = append(attrs.types, attrType)
|
||||
attrs.values = append(attrs.values, value)
|
||||
}
|
||||
|
||||
type sortableAttribute struct {
|
||||
SortKey []byte
|
||||
Attribute attribute
|
||||
}
|
||||
|
||||
type attributeSet []sortableAttribute
|
||||
|
||||
func (sa attributeSet) Len() int {
|
||||
return len(sa)
|
||||
}
|
||||
|
||||
func (sa attributeSet) Less(i, j int) bool {
|
||||
return bytes.Compare(sa[i].SortKey, sa[j].SortKey) < 0
|
||||
}
|
||||
|
||||
func (sa attributeSet) Swap(i, j int) {
|
||||
sa[i], sa[j] = sa[j], sa[i]
|
||||
}
|
||||
|
||||
func (sa attributeSet) attributes() []attribute {
|
||||
attrs := make([]attribute, len(sa))
|
||||
for i, attr := range sa {
|
||||
attrs[i] = attr.Attribute
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
func (attrs *attributes) forMarshaling() ([]attribute, error) {
|
||||
sortables := make(attributeSet, len(attrs.types))
|
||||
for i := range sortables {
|
||||
attrType := attrs.types[i]
|
||||
attrValue := attrs.values[i]
|
||||
asn1Value, err := asn1.Marshal(attrValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attr := attribute{
|
||||
Type: attrType,
|
||||
Value: asn1.RawValue{Tag: 17, IsCompound: true, Bytes: asn1Value}, // 17 == SET tag
|
||||
}
|
||||
encoded, err := asn1.Marshal(attr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sortables[i] = sortableAttribute{
|
||||
SortKey: encoded,
|
||||
Attribute: attr,
|
||||
}
|
||||
}
|
||||
sort.Sort(sortables)
|
||||
return sortables.attributes(), nil
|
||||
}
|
||||
|
||||
// addSigner signs attributes about the content and adds certificate to payload
|
||||
func (sd *SignedData) addSigner(cert *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error {
|
||||
attrs := &attributes{}
|
||||
attrs.Add(oidAttributeContentType, sd.sd.ContentInfo.ContentType)
|
||||
attrs.Add(oidAttributeMessageDigest, sd.messageDigest)
|
||||
attrs.Add(oidAttributeSigningTime, time.Now())
|
||||
for _, attr := range config.ExtraSignedAttributes {
|
||||
attrs.Add(attr.Type, attr.Value)
|
||||
}
|
||||
finalAttrs, err := attrs.forMarshaling()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signature, err := signAttributes(finalAttrs, pkey, crypto.SHA1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ias, err := cert2issuerAndSerial(cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signer := signerInfo{
|
||||
AuthenticatedAttributes: finalAttrs,
|
||||
DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oidDigestAlgorithmSHA1},
|
||||
DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oidSignatureSHA1WithRSA},
|
||||
IssuerAndSerialNumber: ias,
|
||||
EncryptedDigest: signature,
|
||||
Version: 1,
|
||||
}
|
||||
// create signature of signed attributes
|
||||
sd.certs = append(sd.certs, cert)
|
||||
sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer)
|
||||
return nil
|
||||
}
|
||||
|
||||
// addCertificate adds the certificate to the payload. Useful for parent certificates
|
||||
func (sd *SignedData) addCertificate(cert *x509.Certificate) {
|
||||
sd.certs = append(sd.certs, cert)
|
||||
}
|
||||
|
||||
// detach removes content from the signed data struct to make it a detached signature.
|
||||
// This must be called right before Finish()
|
||||
func (sd *SignedData) detach() {
|
||||
sd.sd.ContentInfo = contentInfo{ContentType: oidData}
|
||||
}
|
||||
|
||||
// finish marshals the content and its signers
|
||||
func (sd *SignedData) finish() ([]byte, error) {
|
||||
sd.sd.Certificates = marshalCertificates(sd.certs)
|
||||
inner, err := asn1.Marshal(sd.sd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outer := contentInfo{
|
||||
ContentType: oidSignedData,
|
||||
Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: inner, IsCompound: true},
|
||||
}
|
||||
return asn1.Marshal(outer)
|
||||
}
|
||||
|
||||
func cert2issuerAndSerial(cert *x509.Certificate) (issuerAndSerial, error) {
|
||||
var ias issuerAndSerial
|
||||
// The issuer RDNSequence has to match exactly the sequence in the certificate
|
||||
// We cannot use cert.Issuer.ToRDNSequence() here since it mangles the sequence
|
||||
ias.IssuerName = asn1.RawValue{FullBytes: cert.RawIssuer}
|
||||
ias.SerialNumber = cert.SerialNumber
|
||||
|
||||
return ias, nil
|
||||
}
|
||||
|
||||
// signs the DER encoded form of the attributes with the private key
|
||||
func signAttributes(attrs []attribute, pkey crypto.PrivateKey, hash crypto.Hash) ([]byte, error) {
|
||||
attrBytes, err := marshalAttributes(attrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h := hash.New()
|
||||
h.Write(attrBytes)
|
||||
hashed := h.Sum(nil)
|
||||
switch priv := pkey.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA1, hashed)
|
||||
}
|
||||
return nil, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
// concats and wraps the certificates in the RawValue structure
|
||||
func marshalCertificates(certs []*x509.Certificate) rawCertificates {
|
||||
var buf bytes.Buffer
|
||||
for _, cert := range certs {
|
||||
buf.Write(cert.Raw)
|
||||
}
|
||||
rawCerts, _ := marshalCertificateBytes(buf.Bytes())
|
||||
return rawCerts
|
||||
}
|
||||
|
||||
// Even though, the tag & length are stripped out during marshalling the
|
||||
// RawContent, we have to encode it into the RawContent. If its missing,
|
||||
// then `asn1.Marshal()` will strip out the certificate wrapper instead.
|
||||
func marshalCertificateBytes(certs []byte) (rawCertificates, error) {
|
||||
var val = asn1.RawValue{Bytes: certs, Class: 2, Tag: 0, IsCompound: true}
|
||||
b, err := asn1.Marshal(val)
|
||||
if err != nil {
|
||||
return rawCertificates{}, err
|
||||
}
|
||||
return rawCertificates{Raw: b}, nil
|
||||
}
|
189
pkcs7_test.go
Normal file
189
pkcs7_test.go
Normal file
|
@ -0,0 +1,189 @@
|
|||
// SPDX-FileCopyrightText: 2022-2023 The go-mail Authors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package mail
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestSign_E2E tests S/MIME singing as e2e
|
||||
func TestSign_E2E(t *testing.T) {
|
||||
cert, err := createTestCertificate()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
content := []byte("Hello World")
|
||||
for _, testDetach := range []bool{false, true} {
|
||||
toBeSigned, err := newSignedData(content)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot initialize signed data: %s", err)
|
||||
}
|
||||
if err := toBeSigned.addSigner(cert.Certificate, cert.PrivateKey, SignerInfoConfig{}); err != nil {
|
||||
t.Fatalf("Cannot add signer: %s", err)
|
||||
}
|
||||
if testDetach {
|
||||
t.Log("Testing detached signature")
|
||||
toBeSigned.detach()
|
||||
} else {
|
||||
t.Log("Testing attached signature")
|
||||
}
|
||||
signed, err := toBeSigned.finish()
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot finish signing data: %s", err)
|
||||
}
|
||||
if err := pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: signed}); err != nil {
|
||||
t.Fatalf("Cannot write signed data: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenSSLVerifyDetachedSignature(t *testing.T) {
|
||||
rootCert, err := createTestCertificateByIssuer("PKCS7 Test Root CA", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot generate root cert: %s", err)
|
||||
}
|
||||
signerCert, err := createTestCertificateByIssuer("PKCS7 Test Signer Cert", rootCert)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot generate signer cert: %s", err)
|
||||
}
|
||||
content := []byte("Hello World")
|
||||
toBeSigned, err := newSignedData(content)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot initialize signed data: %s", err)
|
||||
}
|
||||
if err := toBeSigned.addSigner(signerCert.Certificate, signerCert.PrivateKey, SignerInfoConfig{}); err != nil {
|
||||
t.Fatalf("Cannot add signer: %s", err)
|
||||
}
|
||||
toBeSigned.detach()
|
||||
signed, err := toBeSigned.finish()
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot finish signing data: %s", err)
|
||||
}
|
||||
|
||||
// write the root cert to a temp file
|
||||
tmpRootCertFile, err := ioutil.TempFile("", "pkcs7TestRootCA")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tmpRootCertFile.Name()) // clean up
|
||||
fd, err := os.OpenFile(tmpRootCertFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pem.Encode(fd, &pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Certificate.Raw})
|
||||
fd.Close()
|
||||
|
||||
// write the signature to a temp file
|
||||
tmpSignatureFile, err := ioutil.TempFile("", "pkcs7Signature")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tmpSignatureFile.Name()) // clean up
|
||||
ioutil.WriteFile(tmpSignatureFile.Name(), signed, 0755)
|
||||
|
||||
// write the content to a temp file
|
||||
tmpContentFile, err := ioutil.TempFile("", "pkcs7Content")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tmpContentFile.Name()) // clean up
|
||||
ioutil.WriteFile(tmpContentFile.Name(), content, 0755)
|
||||
|
||||
// call openssl to verify the signature on the content using the root
|
||||
opensslCMD := exec.Command("openssl", "smime", "-verify",
|
||||
"-in", tmpSignatureFile.Name(), "-inform", "DER",
|
||||
"-content", tmpContentFile.Name(),
|
||||
"-CAfile", tmpRootCertFile.Name())
|
||||
out, err := opensslCMD.Output()
|
||||
t.Logf("%s", out)
|
||||
if err != nil {
|
||||
t.Fatalf("openssl command failed with %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
type certKeyPair struct {
|
||||
Certificate *x509.Certificate
|
||||
PrivateKey *rsa.PrivateKey
|
||||
}
|
||||
|
||||
func createTestCertificate() (*certKeyPair, error) {
|
||||
signer, err := createTestCertificateByIssuer("Eddard Stark", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Println("Created root cert")
|
||||
if err := pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: signer.Certificate.Raw}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pair, err := createTestCertificateByIssuer("Jon Snow", signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Println("Created signer cert")
|
||||
if err := pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: pair.Certificate.Raw}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pair, nil
|
||||
}
|
||||
|
||||
func createTestCertificateByIssuer(name string, issuer *certKeyPair) (*certKeyPair, error) {
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 32)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
SignatureAlgorithm: x509.SHA256WithRSA,
|
||||
Subject: pkix.Name{
|
||||
CommonName: name,
|
||||
Organization: []string{"Acme Co"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(1, 0, 0),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection},
|
||||
}
|
||||
var issuerCert *x509.Certificate
|
||||
var issuerKey crypto.PrivateKey
|
||||
if issuer != nil {
|
||||
issuerCert = issuer.Certificate
|
||||
issuerKey = issuer.PrivateKey
|
||||
} else {
|
||||
template.IsCA = true
|
||||
template.KeyUsage |= x509.KeyUsageCertSign
|
||||
issuerCert = &template
|
||||
issuerKey = priv
|
||||
}
|
||||
cert, err := x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.Public(), issuerKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
leaf, err := x509.ParseCertificate(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &certKeyPair{
|
||||
Certificate: leaf,
|
||||
PrivateKey: priv,
|
||||
}, nil
|
||||
}
|
Loading…
Reference in a new issue