This repository has been archived by the owner on May 13, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Remove replace directive for github.com/github/smimesign
This forks the changes made in github/smimesign#108 into this repo. This was done because the replace directive was preventing go install from functioning properly. Once the upstream PR is merged, we can revert this change.
- Loading branch information
Showing
8 changed files
with
246 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package signature | ||
|
||
import ( | ||
"bytes" | ||
"crypto" | ||
"crypto/x509" | ||
"encoding/pem" | ||
|
||
cms "github.com/github/smimesign/ietf-cms" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
type SignOptions struct { | ||
// Make a detached signature | ||
Detached bool | ||
// URL of RFC3161 timestamp authority to use for timestamping | ||
TimestampAuthority string | ||
// Create ascii armored output | ||
Armor bool | ||
// IncludeCerts specifies what certs to include in the resulting signature. | ||
// -3 is the same as -2, but ommits issuer when cert has Authority Information Access extension. | ||
// -2 includes all certs except root. | ||
// -1 includes all certs. | ||
// 0 includes no certs. | ||
// 1 includes leaf cert. | ||
// >1 includes n from the leaf. | ||
IncludeCerts int | ||
} | ||
|
||
// Identity is a copy of smimesign.Identity to allow for compatibility without | ||
// needing a dependency on the whole package. This can be removed once | ||
// https://github.com/github/smimesign/pull/108 is merged. | ||
type Identity interface { | ||
// Certificate gets the identity's certificate. | ||
Certificate() (*x509.Certificate, error) | ||
// CertificateChain attempts to get the identity's full certificate chain. | ||
CertificateChain() ([]*x509.Certificate, error) | ||
// Signer gets a crypto.Signer that uses the identity's private key. | ||
Signer() (crypto.Signer, error) | ||
// Delete deletes this identity from the system. | ||
Delete() error | ||
// Close any manually managed memory held by the Identity. | ||
Close() | ||
} | ||
|
||
// Sign signs a given payload for the given identity. | ||
// The resulting signature and cert used is returned. | ||
func Sign(ident Identity, body []byte, opts SignOptions) ([]byte, *x509.Certificate, error) { | ||
cert, err := ident.Certificate() | ||
if err != nil { | ||
return nil, nil, errors.Wrap(err, "failed to get idenity certificate") | ||
} | ||
signer, err := ident.Signer() | ||
if err != nil { | ||
return nil, nil, errors.Wrap(err, "failed to get idenity signer") | ||
} | ||
|
||
sd, err := cms.NewSignedData(body) | ||
if err != nil { | ||
return nil, nil, errors.Wrap(err, "failed to create signed data") | ||
} | ||
|
||
if err := sd.Sign([]*x509.Certificate{cert}, signer); err != nil { | ||
return nil, nil, errors.Wrap(err, "failed to sign message") | ||
} | ||
if opts.Detached { | ||
sd.Detached() | ||
} | ||
|
||
if len(opts.TimestampAuthority) > 0 { | ||
if err = sd.AddTimestamps(opts.TimestampAuthority); err != nil { | ||
return nil, nil, errors.Wrap(err, "failed to add timestamp") | ||
} | ||
} | ||
|
||
chain, err := ident.CertificateChain() | ||
if err != nil { | ||
return nil, nil, errors.Wrap(err, "failed to get idenity certificate chain") | ||
} | ||
if chain, err = certsForSignature(chain, opts.IncludeCerts); err != nil { | ||
return nil, nil, err | ||
} | ||
if err := sd.SetCertificates(chain); err != nil { | ||
return nil, nil, errors.Wrap(err, "failed to set certificates") | ||
} | ||
|
||
der, err := sd.ToDER() | ||
if err != nil { | ||
return nil, nil, errors.Wrap(err, "failed to serialize signature") | ||
} | ||
|
||
if opts.Armor { | ||
return pem.EncodeToMemory(&pem.Block{ | ||
Type: "SIGNED MESSAGE", | ||
Bytes: der, | ||
}), cert, nil | ||
} else { | ||
return der, cert, nil | ||
} | ||
} | ||
|
||
// certsForSignature determines which certificates to include in the signature | ||
// based on the --include-certs option specified by the user. | ||
func certsForSignature(chain []*x509.Certificate, include int) ([]*x509.Certificate, error) { | ||
if include < -3 { | ||
include = -2 // default | ||
} | ||
if include > len(chain) { | ||
include = len(chain) | ||
} | ||
|
||
switch include { | ||
case -3: | ||
for i := len(chain) - 1; i > 0; i-- { | ||
issuer, cert := chain[i], chain[i-1] | ||
|
||
// remove issuer when cert has AIA extension | ||
if bytes.Equal(issuer.RawSubject, cert.RawIssuer) && len(cert.IssuingCertificateURL) > 0 { | ||
chain = chain[0:i] | ||
} | ||
} | ||
return chainWithoutRoot(chain), nil | ||
case -2: | ||
return chainWithoutRoot(chain), nil | ||
case -1: | ||
return chain, nil | ||
default: | ||
return chain[0:include], nil | ||
} | ||
} | ||
|
||
// Returns the provided chain, having removed the root certificate, if present. | ||
// This includes removing the cert itself if the chain is a single self-signed | ||
// cert. | ||
func chainWithoutRoot(chain []*x509.Certificate) []*x509.Certificate { | ||
if len(chain) == 0 { | ||
return chain | ||
} | ||
|
||
lastIdx := len(chain) - 1 | ||
last := chain[lastIdx] | ||
|
||
if bytes.Equal(last.RawIssuer, last.RawSubject) { | ||
return chain[0:lastIdx] | ||
} | ||
|
||
return chain | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package signature | ||
|
||
import ( | ||
"crypto" | ||
"crypto/x509" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/github/smimesign/fakeca" | ||
) | ||
|
||
type identity struct { | ||
Identity | ||
base *fakeca.Identity | ||
} | ||
|
||
func (i *identity) Certificate() (*x509.Certificate, error) { | ||
return i.base.Certificate, nil | ||
} | ||
|
||
func (i *identity) CertificateChain() ([]*x509.Certificate, error) { | ||
return i.base.Chain(), nil | ||
} | ||
|
||
func (i *identity) Signer() (crypto.Signer, error) { | ||
return i.base.PrivateKey, nil | ||
} | ||
|
||
// TestSignVerify is a basic test to ensure that the Sign/Verify funcs can be | ||
// used with each other. We're assuming that the actual signature format has | ||
// been more thoroghly vetted in other packages (i.e. ietf-cms). | ||
func TestSignVerify(t *testing.T) { | ||
id := &identity{ | ||
base: fakeca.New(), | ||
} | ||
data := []byte("tacocat") | ||
|
||
sig, _, err := Sign(id, data, SignOptions{ | ||
Detached: true, | ||
Armor: true, | ||
// Fake CA outputs self-signed certs, so we need to use -1 to make sure | ||
// the self-signed cert itself is included in the chain, otherwise | ||
// Verify cannot find a cert to use for verification. | ||
IncludeCerts: -1, | ||
}) | ||
if err != nil { | ||
t.Fatalf("Sign() = %v", err) | ||
} | ||
|
||
fmt.Println(id.base.Chain()) | ||
if _, err := Verify(data, sig, true, x509.VerifyOptions{ | ||
// Trust the fake CA | ||
Roots: id.base.ChainPool(), | ||
}); err != nil { | ||
t.Fatalf("Verify() = %v", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package signature | ||
|
||
import ( | ||
"crypto/x509" | ||
"encoding/pem" | ||
|
||
cms "github.com/github/smimesign/ietf-cms" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// Verify verifies a signature for a given identity. | ||
// | ||
// WARNING: this function doesn't do any revocation checking. | ||
func Verify(body, sig []byte, detached bool, opts x509.VerifyOptions) ([][][]*x509.Certificate, error) { | ||
// Try decoding as PEM | ||
var der []byte | ||
if blk, _ := pem.Decode(sig); blk != nil { | ||
der = blk.Bytes | ||
} else { | ||
der = sig | ||
} | ||
|
||
// Parse signature | ||
sd, err := cms.ParseSignedData(der) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to parse signature") | ||
} | ||
|
||
if detached { | ||
return sd.VerifyDetached(body, opts) | ||
} else { | ||
return sd.Verify(opts) | ||
} | ||
} |