forked from fullsailor/pkcs7
-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from mozilla-services/verifycertchain
Add support for certificate chains
- Loading branch information
Showing
14 changed files
with
2,253 additions
and
1,377 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,26 @@ | ||
PROJECT := go.mozilla.org/pkcs7 | ||
|
||
all: test vet lint | ||
all: vet lint unused gosimple staticcheck test | ||
|
||
test: | ||
go test $(PROJECT) | ||
go test -covermode=count -coverprofile=coverage.out . | ||
|
||
showcoverage: test | ||
go tool cover -html=coverage.out | ||
|
||
vet: | ||
go vet $(PROJECT) | ||
go vet . | ||
|
||
lint: | ||
golint $(PROJECT) | ||
golint . | ||
|
||
unused: | ||
unused . | ||
|
||
gosimple: | ||
gosimple . | ||
|
||
staticcheck: | ||
staticcheck . | ||
|
||
gettools: | ||
go get -u honnef.co/go/tools/... | ||
go get -u github.com/golang/lint/golint |
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,166 @@ | ||
package pkcs7 | ||
|
||
import ( | ||
"bytes" | ||
"crypto" | ||
"crypto/aes" | ||
"crypto/cipher" | ||
"crypto/des" | ||
"crypto/rand" | ||
"crypto/rsa" | ||
"crypto/x509" | ||
"encoding/asn1" | ||
"errors" | ||
"fmt" | ||
) | ||
|
||
// 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") | ||
|
||
// ErrNotEncryptedContent is returned when attempting to Decrypt data that is not encrypted data | ||
var ErrNotEncryptedContent = errors.New("pkcs7: content data is a decryptable data type") | ||
|
||
// Decrypt decrypts encrypted content info for recipient cert and private key | ||
func (p7 *PKCS7) Decrypt(cert *x509.Certificate, pkey crypto.PrivateKey) ([]byte, error) { | ||
data, ok := p7.raw.(envelopedData) | ||
if !ok { | ||
return nil, ErrNotEncryptedContent | ||
} | ||
recipient := selectRecipientForCertificate(data.RecipientInfos, cert) | ||
if recipient.EncryptedKey == nil { | ||
return nil, errors.New("pkcs7: no enveloped recipient for provided certificate") | ||
} | ||
switch pkey.(type) { | ||
case *rsa.PrivateKey: | ||
var contentKey []byte | ||
contentKey, err := rsa.DecryptPKCS1v15(rand.Reader, pkey.(*rsa.PrivateKey), recipient.EncryptedKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return data.EncryptedContentInfo.decrypt(contentKey) | ||
} | ||
return nil, ErrUnsupportedAlgorithm | ||
} | ||
|
||
func (eci encryptedContentInfo) decrypt(key []byte) ([]byte, error) { | ||
alg := eci.ContentEncryptionAlgorithm.Algorithm | ||
if !alg.Equal(oidEncryptionAlgorithmDESCBC) && | ||
!alg.Equal(oidEncryptionAlgorithmDESEDE3CBC) && | ||
!alg.Equal(oidEncryptionAlgorithmAES256CBC) && | ||
!alg.Equal(oidEncryptionAlgorithmAES128CBC) && | ||
!alg.Equal(oidEncryptionAlgorithmAES128GCM) { | ||
fmt.Printf("Unsupported Content Encryption Algorithm: %s\n", alg) | ||
return nil, ErrUnsupportedAlgorithm | ||
} | ||
|
||
// EncryptedContent can either be constructed of multple OCTET STRINGs | ||
// or _be_ a tagged OCTET STRING | ||
var cyphertext []byte | ||
if eci.EncryptedContent.IsCompound { | ||
// Complex case to concat all of the children OCTET STRINGs | ||
var buf bytes.Buffer | ||
cypherbytes := eci.EncryptedContent.Bytes | ||
for { | ||
var part []byte | ||
cypherbytes, _ = asn1.Unmarshal(cypherbytes, &part) | ||
buf.Write(part) | ||
if cypherbytes == nil { | ||
break | ||
} | ||
} | ||
cyphertext = buf.Bytes() | ||
} else { | ||
// Simple case, the bytes _are_ the cyphertext | ||
cyphertext = eci.EncryptedContent.Bytes | ||
} | ||
|
||
var block cipher.Block | ||
var err error | ||
|
||
switch { | ||
case alg.Equal(oidEncryptionAlgorithmDESCBC): | ||
block, err = des.NewCipher(key) | ||
case alg.Equal(oidEncryptionAlgorithmDESEDE3CBC): | ||
block, err = des.NewTripleDESCipher(key) | ||
case alg.Equal(oidEncryptionAlgorithmAES256CBC): | ||
fallthrough | ||
case alg.Equal(oidEncryptionAlgorithmAES128GCM), alg.Equal(oidEncryptionAlgorithmAES128CBC): | ||
block, err = aes.NewCipher(key) | ||
} | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if alg.Equal(oidEncryptionAlgorithmAES128GCM) { | ||
params := aesGCMParameters{} | ||
paramBytes := eci.ContentEncryptionAlgorithm.Parameters.Bytes | ||
|
||
_, err := asn1.Unmarshal(paramBytes, ¶ms) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
gcm, err := cipher.NewGCM(block) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if len(params.Nonce) != gcm.NonceSize() { | ||
return nil, errors.New("pkcs7: encryption algorithm parameters are incorrect") | ||
} | ||
if params.ICVLen != gcm.Overhead() { | ||
return nil, errors.New("pkcs7: encryption algorithm parameters are incorrect") | ||
} | ||
|
||
plaintext, err := gcm.Open(nil, params.Nonce, cyphertext, nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return plaintext, nil | ||
} | ||
|
||
iv := eci.ContentEncryptionAlgorithm.Parameters.Bytes | ||
if len(iv) != block.BlockSize() { | ||
return nil, errors.New("pkcs7: encryption algorithm parameters are malformed") | ||
} | ||
mode := cipher.NewCBCDecrypter(block, iv) | ||
plaintext := make([]byte, len(cyphertext)) | ||
mode.CryptBlocks(plaintext, cyphertext) | ||
if plaintext, err = unpad(plaintext, mode.BlockSize()); err != nil { | ||
return nil, err | ||
} | ||
return plaintext, nil | ||
} | ||
|
||
func unpad(data []byte, blocklen int) ([]byte, error) { | ||
if blocklen < 1 { | ||
return nil, fmt.Errorf("invalid blocklen %d", blocklen) | ||
} | ||
if len(data)%blocklen != 0 || len(data) == 0 { | ||
return nil, fmt.Errorf("invalid data len %d", len(data)) | ||
} | ||
|
||
// the last byte is the length of padding | ||
padlen := int(data[len(data)-1]) | ||
|
||
// check padding integrity, all bytes should be the same | ||
pad := data[len(data)-padlen:] | ||
for _, padbyte := range pad { | ||
if padbyte != byte(padlen) { | ||
return nil, errors.New("invalid padding") | ||
} | ||
} | ||
|
||
return data[:len(data)-padlen], nil | ||
} | ||
|
||
func selectRecipientForCertificate(recipients []recipientInfo, cert *x509.Certificate) recipientInfo { | ||
for _, recp := range recipients { | ||
if isCertMatchForIssuerAndSerial(cert, recp.IssuerAndSerialNumber) { | ||
return recp | ||
} | ||
} | ||
return recipientInfo{} | ||
} |
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,60 @@ | ||
package pkcs7 | ||
|
||
import ( | ||
"bytes" | ||
"testing" | ||
) | ||
|
||
func TestDecrypt(t *testing.T) { | ||
fixture := UnmarshalTestFixture(EncryptedTestFixture) | ||
p7, err := Parse(fixture.Input) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
content, err := p7.Decrypt(fixture.Certificate, fixture.PrivateKey) | ||
if err != nil { | ||
t.Errorf("Cannot Decrypt with error: %v", err) | ||
} | ||
expected := []byte("This is a test") | ||
if !bytes.Equal(content, expected) { | ||
t.Errorf("Decrypted result does not match.\n\tExpected:%s\n\tActual:%s", expected, content) | ||
} | ||
} | ||
|
||
// Content is "This is a test" | ||
var EncryptedTestFixture = ` | ||
-----BEGIN PKCS7----- | ||
MIIBFwYJKoZIhvcNAQcDoIIBCDCCAQQCAQAxgcowgccCAQAwMjApMRAwDgYDVQQK | ||
EwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3RhcmsCBQDL+CvWMAsGCSqGSIb3 | ||
DQEBAQSBgKyP/5WlRTZD3dWMrLOX6QRNDrXEkQjhmToRwFZdY3LgUh25ZU0S/q4G | ||
dHPV21Fv9lQD+q7l3vfeHw8M6Z1PKi9sHMVfxAkQpvaI96DTIT3YHtuLC1w3geCO | ||
8eFWTq2qS4WChSuS/yhYosjA1kTkE0eLnVZcGw0z/WVuEZznkdyIMDIGCSqGSIb3 | ||
DQEHATARBgUrDgMCBwQImpKsUyMPpQigEgQQRcWWrCRXqpD5Njs0GkJl+g== | ||
-----END PKCS7----- | ||
-----BEGIN CERTIFICATE----- | ||
MIIB1jCCAUGgAwIBAgIFAMv4K9YwCwYJKoZIhvcNAQELMCkxEDAOBgNVBAoTB0Fj | ||
bWUgQ28xFTATBgNVBAMTDEVkZGFyZCBTdGFyazAeFw0xNTA1MDYwMzU2NDBaFw0x | ||
NjA1MDYwMzU2NDBaMCUxEDAOBgNVBAoTB0FjbWUgQ28xETAPBgNVBAMTCEpvbiBT | ||
bm93MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK6NU0R0eiCYVquU4RcjKc | ||
LzGfx0aa1lMr2TnLQUSeLFZHFxsyyMXXuMPig3HK4A7SGFHupO+/1H/sL4xpH5zg | ||
8+Zg2r8xnnney7abxcuv0uATWSIeKlNnb1ZO1BAxFnESc3GtyOCr2dUwZHX5mRVP | ||
+Zxp2ni5qHNraf3wE2VPIQIDAQABoxIwEDAOBgNVHQ8BAf8EBAMCAKAwCwYJKoZI | ||
hvcNAQELA4GBAIr2F7wsqmEU/J/kLyrCgEVXgaV/sKZq4pPNnzS0tBYk8fkV3V18 | ||
sBJyHKRLL/wFZASvzDcVGCplXyMdAOCyfd8jO3F9Ac/xdlz10RrHJT75hNu3a7/n | ||
9KNwKhfN4A1CQv2x372oGjRhCW5bHNCWx4PIVeNzCyq/KZhyY9sxHE6f | ||
-----END CERTIFICATE----- | ||
-----BEGIN PRIVATE KEY----- | ||
MIICXgIBAAKBgQDK6NU0R0eiCYVquU4RcjKcLzGfx0aa1lMr2TnLQUSeLFZHFxsy | ||
yMXXuMPig3HK4A7SGFHupO+/1H/sL4xpH5zg8+Zg2r8xnnney7abxcuv0uATWSIe | ||
KlNnb1ZO1BAxFnESc3GtyOCr2dUwZHX5mRVP+Zxp2ni5qHNraf3wE2VPIQIDAQAB | ||
AoGBALyvnSt7KUquDen7nXQtvJBudnf9KFPt//OjkdHHxNZNpoF/JCSqfQeoYkeu | ||
MdAVYNLQGMiRifzZz4dDhA9xfUAuy7lcGQcMCxEQ1dwwuFaYkawbS0Tvy2PFlq2d | ||
H5/HeDXU4EDJ3BZg0eYj2Bnkt1sJI35UKQSxblQ0MY2q0uFBAkEA5MMOogkgUx1C | ||
67S1tFqMUSM8D0mZB0O5vOJZC5Gtt2Urju6vywge2ArExWRXlM2qGl8afFy2SgSv | ||
Xk5eybcEiQJBAOMRwwbEoW5NYHuFFbSJyWll4n71CYuWuQOCzehDPyTb80WFZGLV | ||
i91kFIjeERyq88eDE5xVB3ZuRiXqaShO/9kCQQCKOEkpInaDgZSjskZvuJ47kByD | ||
6CYsO4GIXQMMeHML8ncFH7bb6AYq5ybJVb2NTU7QLFJmfeYuhvIm+xdOreRxAkEA | ||
o5FC5Jg2FUfFzZSDmyZ6IONUsdF/i78KDV5nRv1R+hI6/oRlWNCtTNBv/lvBBd6b | ||
dseUE9QoaQZsn5lpILEvmQJAZ0B+Or1rAYjnbjnUhdVZoy9kC4Zov+4UH3N/BtSy | ||
KJRWUR0wTWfZBPZ5hAYZjTBEAFULaYCXlQKsODSp0M1aQA== | ||
-----END PRIVATE KEY-----` |
Oops, something went wrong.