Added a dedicated revocation subpackage

The newly created dedicated revocation package is used to encode and
decode revocation information. Signing and verification can now use the
same structures.

While the InfoArchival stucsture can now be created this stucture is currenlty
not embedded into the document. Test cases for obtaining and embedding revocation
information into the InfoArchival struct are also availible.
This commit is contained in:
Paul van Brouwershaven
2017-07-12 20:53:06 +02:00
parent 099b765c01
commit c327f686d5
4 changed files with 265 additions and 16 deletions

55
revocation/revocation.go Normal file
View File

@@ -0,0 +1,55 @@
package revocation
import (
"encoding/asn1"
"crypto/x509"
)
// InfoArchival is the pkcs7 container containing the revocation information for
// all embedded certificates.
//
// Currently the internal structure is exposed but I don't like to expose the
// asn1.RawValue objects. We can probably make them private and expose the
// information with functions.
type InfoArchival struct {
CRL CRL `asn1:"tag:0,optional,explicit"`
OCSP OCSP `asn1:"tag:1,optional,explicit"`
Other Other `asn1:"tag:2,optional,explicit"`
}
// AddCRL is used to embed an CRL to revocation.InfoArchival object. You directly
// pass the bytes of a downloaded CRL to this function.
func (r *InfoArchival) AddCRL(b []byte) error {
r.CRL = append(r.CRL, asn1.RawValue{FullBytes: b})
return nil
}
// AddOCSP is used to embed the raw bytes of an OCSP response.
func (r *InfoArchival) AddOCSP(b []byte) error {
r.OCSP = append(r.OCSP, asn1.RawValue{FullBytes: b})
return nil
}
// IsRevoked checks if there is a status inclded for the certificate and returns
// true if the certificate is marked as revoked.
//
// TODO: We should report if there is no CRL or OCSP response embeded for this certificate
// TODO: Information about the revocation (time, reason, etc) must be extractable
func (r *InfoArchival) IsRevoked(c *x509.Certificate) bool {
// check the crl and ocsp to see if this certificate is revoked
return true
}
// CRL contains the raw bytes of a pkix.CertificateList and can be parsed with
// x509.PParseCRL.
type CRL []asn1.RawValue
// OCSP contains the raw bytes of an OCSP response and can be parsed with
// x/crypto/ocsp.ParseResponse
type OCSP []asn1.RawValue
// ANS.1 Object OtherRevInfo
type Other struct {
Type asn1.ObjectIdentifier
Value []byte
}

88
sign/revocation.go Normal file
View File

@@ -0,0 +1,88 @@
package sign
import (
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"
"bitbucket.org/digitorus/pdfsign/revocation"
"golang.org/x/crypto/ocsp"
)
func embedOCSPRevocationStatus(cert, issuer *x509.Certificate, i *revocation.InfoArchival) error {
req, err := ocsp.CreateRequest(cert, issuer, nil)
if err != nil {
return err
}
ocspUrl := fmt.Sprintf("%s/%s", strings.TrimRight(cert.OCSPServer[0], "/"),
base64.StdEncoding.EncodeToString(req))
resp, err := http.Get(ocspUrl)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
// check if we got a valid OCSP response
_, err = ocsp.ParseResponseForCert(body, cert, issuer)
if err != nil {
return err
}
i.AddOCSP(body)
return nil
}
// embedCRLRevocationStatus requires an issuer as it needs to implement the
// the interface, a nil argment might be given if the issuer is not known.
func embedCRLRevocationStatus(cert, issuer *x509.Certificate, i *revocation.InfoArchival) error {
resp, err := http.Get(cert.CRLDistributionPoints[0])
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
// TODO: verify crl and certificate before embedding
i.AddCRL(body)
return nil
}
func embedRevocationStatus(cert, issuer *x509.Certificate, i *revocation.InfoArchival) error {
// For each certificate a revoction status needs to be included, this can be done
// by embedding a CRL or OCSP response. In most cases an OCSP response is smaller
// to embed in the document but and empty CRL (often seen of dediced high volume
// hirachies) can be smaller.
//
// There have been some reports that the usage of a CRL would result in a better
// compatibilty.
//
// TODO: Find and embed link about compatibilty
// TODO: Implement revocation status caching (required for higher volume signing)
// using an OCSP server
if len(cert.OCSPServer) > 0 {
embedOCSPRevocationStatus(cert, issuer, i)
return nil
}
// using a crl
if len(cert.CRLDistributionPoints) > 0 {
embedCRLRevocationStatus(cert, issuer, i)
return nil
}
return errors.New("certificate contains no information to check status")
}

119
sign/revocation_test.go Normal file
View File

@@ -0,0 +1,119 @@
package sign
import (
"crypto/x509"
"encoding/pem"
"testing"
"bitbucket.org/digitorus/pdfsign/revocation"
)
const certPem = `-----BEGIN CERTIFICATE-----
MIIGKDCCBRCgAwIBAgIMW8J4m7huCPO5f+wbMA0GCSqGSIb3DQEBCwUAMEsxCzAJ
BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSEwHwYDVQQDExhH
bG9iYWxTaWduIENBIDIgZm9yIEFBVEwwHhcNMTcwNzA2MDk0MDUyWhcNMjAwNzA2
MDk0MDUyWjCBpzELMAkGA1UEBhMCR0IxDTALBgNVBAgTBEtlbnQxEjAQBgNVBAcT
CU1haWRzdG9uZTEfMB0GA1UEChMWR01PIEdsb2JhbFNpZ24gTGltaXRlZDEfMB0G
A1UEAxMWUGF1bCBWYW4gQnJvdXdlcnNoYXZlbjEzMDEGCSqGSIb3DQEJARYkcGF1
bC52YW5icm91d2Vyc2hhdmVuQGdsb2JhbHNpZ24uY29tMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAr5jbAIZDjkWngxlwJqneE9VEDTvmMIGwvgy71g5j
k+igHxB6tTfaqGD87oIm2wcrlZHpPJG9n2Rh9FhFmvx8ZXiceNI9Ks5Ho5iYNFUk
y2JuVfFPxtp6amqpLzM5HZUePgu1Gdy1Zn1PUajii7paFPuhdemcA9DdTAQ1GsDv
C9MZ2D5sKM0hLCRePCzJ3TeQmHefFrC0XQ7u2i7LDD990URiFz7WNq2tSDJwBe/6
1tMewpekmtE5X43PqzgcyGBsDJqAKcthsLQnhqrIryuwE2bEhP/FxQrHkT+f/OkK
vwAjB9gYgJRNoMFUwXLW789JsOkbCqepoWsg1PnWrAbAMQIDAQABo4ICrTCCAqkw
DgYDVR0PAQH/BAQDAgeAMIGIBggrBgEFBQcBAQR8MHowQQYIKwYBBQUHMAKGNWh0
dHA6Ly9zZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2dzYWF0bDJzaGEyZzIu
Y3J0MDUGCCsGAQUFBzABhilodHRwOi8vb2NzcDIuZ2xvYmFsc2lnbi5jb20vZ3Nh
YXRsMnNoYTJnMjCB2wYDVR0gBIHTMIHQMIHNBgsrBgEEAaAyASgeAjCBvTCBhgYI
KwYBBQUHAgIwegx4VGhpcyBjZXJ0aWZpY2F0ZSBoYXMgYmVlbiBpc3N1ZWQgaW4g
YWNjb3JkYW5jZSB3aXRoIHRoZSBHbG9iYWxTaWduIENQUyBsb2NhdGVkIGF0IGh0
dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMDIGCCsGAQUFBwIB
FiZodHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzAJBgNVHRME
AjAAMD8GA1UdHwQ4MDYwNKAyoDCGLmh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20v
Z3MvZ3NhYXRsMnNoYTJnMi5jcmwwLwYDVR0RBCgwJoEkcGF1bC52YW5icm91d2Vy
c2hhdmVuQGdsb2JhbHNpZ24uY29tMFwGCiqGSIb3LwEBCQEETjBMAgEBhkRodHRw
Oi8vYWF0bC10aW1lc3RhbXAuZ2xvYmFsc2lnbi5jb20vdHNhL2FvaGZld2F0MjM4
OTUzNWZuYXNnbmxnNW0yMwEBADATBgNVHSUEDDAKBggrBgEFBQcDBDAdBgNVHQ4E
FgQUtimjVds00OgBZ747tqkKbZJeCCowHwYDVR0jBBgwFoAUxRNOzofGiRsj6EDj
dTKbA3A67+8wDQYJKoZIhvcNAQELBQADggEBACBUFVwUKVpOWt1eLf7lKKPfVhEL
9QrkAkV/UZPMsDwBDIJhphqjCqfbJVTgybm79gUCJiwbarCYOHRgFAdNTPEvEcT0
+XwR6WZcDdfQAtaHfO6X9ExgJv93txoFVcpYLY1hR3o6QdP4VQSDhRTv3bM1j/WC
mcCoiIQz28Y8L+8rRx5J7JAgYpupoU/8sCpidBMhYAGF5p8Z8p0LbqvZndRHaVqp
yXQ0kYj1n45it5FXsKECWZKTx0v4IBySJY3RGpF+5cpPUYulJfINBg7nj7aQG/Uv
qtyxnVAG3W4pTHWd/0Gyc3lrgRtyZy+b9DaxHZ/N6HHNgnRHB4PUkektpX4=
-----END CERTIFICATE-----`
const issuerPem = `-----BEGIN CERTIFICATE-----
MIIEpjCCA46gAwIBAgIORea3r9ymcb22XRTz2sAwDQYJKoZIhvcNAQELBQAwVzEL
MAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExLTArBgNVBAMT
JEdsb2JhbFNpZ24gQ0EgZm9yIEFBVEwgLSBTSEEyNTYgLSBHMjAeFw0xNDEyMTAw
MDAwMDBaFw0yNDEyMTAwMDAwMDBaMEsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBH
bG9iYWxTaWduIG52LXNhMSEwHwYDVQQDExhHbG9iYWxTaWduIENBIDIgZm9yIEFB
VEwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJS4vhNfaSmXtX7bWy
hVhBGqisbqgnX9/K5psHMrkwm10pnicCSmvb6nvgMAPbRPyfpGHj5ArrDli6rCDR
CLR1tjied/6AxQCCPgyvDyEwDWxzytVHkCldzEHjHmc1kL0zI7aQfNrD25xAUnHa
X2jBm71filgduyQBfuLJLlL/NGh46X6eI9xpqBmqwBFa6wHPisnwkAB22CQB/OY3
mnlhLCJmrEL9fGPRDQnc6w849ws3nkKISkEPmTyfUAJUkEgCxOjfiwoNSFjZ99lA
K+ujj90dqYU3mJ7dTEHA2+dFrdvVsoyA4JZBu7utxe2rJgpifO3B/L+f4Cat12kA
2IqtAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB
/wIBADAdBgNVHQ4EFgQUxRNOzofGiRsj6EDjdTKbA3A67+8wHwYDVR0jBBgwFoAU
YMLxUj6tjBPc28oO+grmKiyZS9gwgYYGCCsGAQUFBwEBBHoweDA0BggrBgEFBQcw
AYYoaHR0cDovL29jc3AyLmdsb2JhbHNpZ24uY29tL2dzYWF0bHNoYTJnMjBABggr
BgEFBQcwAoY0aHR0cDovL3NlY3VyZS5nbG9iYWxzaWduLmNvbS9jYWNlcnQvZ3Nh
YXRsc2hhMmcyLmNydDA+BgNVHR8ENzA1MDOgMaAvhi1odHRwOi8vY3JsLmdsb2Jh
bHNpZ24uY29tL2dzL2dzYWF0bHNoYTJnMi5jcmwwRwYDVR0gBEAwPjA8BgRVHSAA
MDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9z
aXRvcnkvMA0GCSqGSIb3DQEBCwUAA4IBAQAkH9vI0bJPzbtf5ikdFb5Kc4L9/IIZ
GPeVbS3UPl8x4WPzmZHwGvSeG53DDcJ9weGHO4PORBgBT5KDZEt7CKXd4vt83xw8
P1kYvvjv4E+8R8VD3hP48zkygHMR3JtBdMPkbNbE10TCb4XmQPpkwGIVhaD7ojtS
+4mPjVts6ZZzbnHI42CbYwdaOf2W8IUu0b1w4T7T5YPfi8rSKwQxIKibFG1mSsOC
vG9tDxJVJUNdiWTPoHGn+n+3qeJvPRgHhicZ+ivOqqmLQSNgtp1WdQ+uJmUqU7EY
htN+laG7bS/8xGTPothL9Abgd/9L3X0KKGUDCdcpzRuy20CI7E4uygD8
-----END CERTIFICATE-----`
func TestEmbedRevocationStatus(t *testing.T) {
var ia revocation.InfoArchival
err := embedRevocationStatus(pemToCert(certPem), pemToCert(issuerPem), &ia)
if err != nil {
t.Errorf("%s", err.Error())
}
if len(ia.OCSP) != 1 {
t.Errorf("Expected one OCSP status")
}
}
func TestEmbedOCSPRevocationStatus(t *testing.T) {
var ia revocation.InfoArchival
err := embedOCSPRevocationStatus(pemToCert(certPem), pemToCert(issuerPem), &ia)
if err != nil {
t.Errorf("%s", err.Error())
}
if len(ia.OCSP) != 1 {
t.Errorf("Expected one OCSP status")
}
}
func TestEmbedCRLRevocationStatus(t *testing.T) {
var ia revocation.InfoArchival
err := embedCRLRevocationStatus(pemToCert(certPem), nil, &ia)
if err != nil {
t.Errorf("%s", err.Error())
}
if len(ia.CRL) != 1 {
t.Errorf("Expected one CRL")
}
}
func pemToCert(p string) *x509.Certificate {
block, _ := pem.Decode([]byte(p))
cert, _ := x509.ParseCertificate(block.Bytes)
return cert
}

View File

@@ -11,26 +11,13 @@ import (
"time"
"bitbucket.org/digitorus/pdf"
"bitbucket.org/digitorus/pdfsign/revocation"
"github.com/digitorus/pkcs7"
"github.com/digitorus/timestamp"
"go/src/log"
"log"
"golang.org/x/crypto/ocsp"
)
type RevocationInfoArchival struct {
CRL RevCRL `asn1:"tag:0,optional,explicit"`
OCSP RevOCSP `asn1:"tag:1,optional,explicit"`
OtherRevInfo OtherRevInfo `asn1:"tag:2,optional,explicit"`
}
type RevCRL []asn1.RawValue
type RevOCSP []asn1.RawValue
type OtherRevInfo struct {
Type asn1.ObjectIdentifier
Value []byte
}
type Response struct {
Error string
@@ -192,7 +179,7 @@ func Verify(file *os.File) (apiResp *Response, err error) {
}
// PDF signature certificate revocation information attribute (1.2.840.113583.1.1.8)
var revInfo RevocationInfoArchival
var revInfo revocation.InfoArchival
p7.UnmarshalSignedAttribute(asn1.ObjectIdentifier{1, 2, 840, 113583, 1, 1, 8}, &revInfo)
// Parse OCSP response