Internet-Draft PKS October 2023
Kwapisiewicz Expires 12 April 2024 [Page]
Workgroup:
Network Working Group
Internet-Draft:
draft-kwapisiewicz-pks-00
Published:
Intended Status:
Informational
Expires:
Author:
W. Kwapisiewicz
Metacode

Private Key Store Protocol

Abstract

This specification describes a simplified protocol for executing asymmetric cryptographic operations on private keys over HTTP [RFC7230]. The protocol abstracts away the actual location and implementation of private key storage from applications that need to use keys.

Status of This Memo

This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.

Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.

Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."

This Internet-Draft will expire on 12 April 2024.

Table of Contents

1. Introduction

User applications that work in security contexts (such as X.509 [RFC5280] or OpenPGP [RFC4880]) with encrypted data or digital signatures may need access to private keys to process the user's data. Previously each application had to implement their own subroutines for cryptographic operations on sensitive keys (e.g. [GNUPG-AGENT]) but this needlessly duplicates effort.

1.1. The Need for Standardization

If there are already agents why is a separate protocol needed? The Private Key Store Protocol solves M x N problem: there are M applications and N ways to access the underlying secret keys (such as [PKCS11], [OPENPGP-CARD] or [TPM-TSS]).

Having one universal protocol removes the need for each client application to implement connectors to all of these standards (c.f. [LSP]).

1.2. Basic Protocol Operation

The Private Key Store Protocol (PKS) will typically be used with applications that need to access sensitive, encrypted data as well as for digital signatures. PKS does not require any kind of wrapping for cryptographic artifacts such as [ASN.1] or OpenPGP [RFC4880] framing and as such is ideal for cross-ecosystem applications. For example exposing cryptographic smartcard for both SSH Agent ([RFC4253], [RFC4716]) as well as OpenPGP [RFC4880] decryption and signing.

Since the Private Key Store Protocol is based on the HTTP protocol [RFC7230] application clients can access private keys over the network even in the most restrictive environments. The key server can additionally expose a uniform interface to its components.

The protocol workflow is as follows (see Figure 1):

  1. The Private Key Store Protocol client starts with an URL for the service, as well as public key that represents the public part of the private key to use.

  2. If the private key is protected by a password or a PIN they are used as an HTTP POST payload to the URL given in 1.

  3. If the key is unprotected the client uses empty body.

  4. If the POST call fails the server responds with an error status code (4xx or 5xx).

  5. If the POST call succeeds the server responds with a success status code (2xx) and the response contains a Location header which points to the location of the unlocked key service.

The unlocked key service URL can then be used for cryptographic operations which depend on the type of the key.

        PKS
        Server

          ^ ,
          | ,
    (1)   | ,
    POST  | ,  Location
     PIN  | ,  of key service
          | v

       PKS client
Figure 1: PKS Protocol key unlock flow

After the unlocked key URL has been retrieved it serves as a "capability" which allows the client to execute cryptographic operations using that key. The operation, as defined in this specification is one of:

  • RSA decryption,

  • EC point derivation,

  • RSA/EC signing.

        PKS
        Server

          ^ ,
          | ,
    (1)   | ,
    POST  | ,  body
    data  | ,  cryptographic result
          | v

       PKS client
Figure 2: PKS Protocol key operation flow

2. Protocol Definition

2.1. Terminology

In this document, the key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" are to be interpreted as described in BCP 14, RFC 2119 [RFC2119] and indicate requirement levels for compliant STuPiD implementations.

2.2. Discovering Unlocked Keys

A client may ask whether the key has been unlocked by simply supplying empty (zero octets) data when executing the initial POST request to the Private Key Server URL.

If the key has been previously unlocked by any out-of-band mechanism the server will reply with successful status code (2xx) and a Location header pointing to the key operations URL.

If the key does not exist or is locked a failed status MUST be returned (4xx).

2.3. Accessing Locked Keys

Keys that are locked (e.g. smartcard-backed keys) need a PIN or password to unlock. This secret octets are sent as a raw body of the initial POST request.

If the key has been found and the unlock operation succeeds the server will reply with successful status code (2xx) and a Location header pointing to the key operations URL.

If the key does not exist or the unlocking operation fails the status code returned MUST be in one of the failed status ranges (4xx or 5xx).

Note that if the key does not exist the response SHOULD be 404 Not Found. If the PIN value is invalid it SHOULD be 403 Forbidden. To increase the privacy the server is allowed to return 404 Not Found in both of these cases.

2.4. Key Unlock Parameters

To identify the key that should be unlocked in the initial request the client additionally supplies several URL parameters. The exact ones depend on the key that is to be unlocked.

Common parameters for key unlock:

  • capability - key capability, SHOULD be one of decrypt or sign,

All algorithm-specific parameters are base64-URL [RFC4648] encoded.

RSA-specific parameters:

  • n - public modulus,

  • e (optional) - public exponent, by default 65537.

EC-specific parameters:

  • p - public point,

  • c - curve OID bytes as defined in [RFC6637].

2.5. Key Unlock Response Headers

If the key unlock operation succeeds the server MUST return a Location header, but MAY return additional headers hinting to the client what the exact key characteristics are.

Response headers:

  • Location - capability URL used for private key operations,

  • Accept-Post - colon-separated list of supported operations on the capability URL.

If the unlock fails, for example due to key not supporting given capability a 406 Not Acceptable error code SHOULD be returned to the client.

2.6. RSA Decryption

The capability URL can be used for RSA decryption. The client sends a POST request to the capability URL with symmetric key octets in the request body. The server replies with the decrypted plaintext.

The request MUST contain a Content-Type header set to application/vnd.pks.rsa.ciphertext.

2.7. ECDH derivation

If the key is an Elliptic Curve key then the capability URL can be used for ECDH point derivation. The POST request to the capability URL with ECDH ephemeral point value octets in the request body will cause the secret key to derive the point and return the S parameter in response body.

The request MUST contain a Content-Type header set to application/vnd.pks.ecdh.point.

2.8. Data Signing

Signing works similarly regardless whether the key is RSA or EC-based. The client prepares the message digest and passes it as a request body in the POST HTTP request. The client application MUST indicate the digest used in the Content-Type header. This is particularly important for PKCS 1.5 RSA signing [RFC3447] which needs to wrap the signed object in an [ASN.1] DigestInfo structure [RFC3447].

The following values for that header are defined in this specification:

  • application/vnd.pks.digest.sha1 - SHA-1 [FIPS-180-2],

  • application/vnd.pks.digest.sha256 - SHA-256 [RFC6234],

  • application/vnd.pks.digest.sha512 - SHA-512 [RFC6234].

The application MAY support other content types. The list of supported types is communicated using the Accept-Post response header.

If the signing succeeds (status code 2xx) the response body will contain raw signature. The signature does not have any framing and the exact meaning of octets is key-dependent and communicated through the Content-Type of the response:

  • application/vnd.pks.signature.rsa - RSA signature [RFC3447],

  • application/vnd.pks.signature.eddsa.rs - R and S values concatenated (64 octets in total) of the EdDSA signature [RFC8032],

  • application/vnd.pks.signature.ecdsa.rs - R and S values concatenated (exact number of octets varies but the number of octets is always even and R and S always are of the same size) of the ECDSA signature [RFC6979].

3. Implementation Notes

A Private Key Store client SHOULD treat the capability URLs with care. Since these URLs allow accessing unlocked private keys they SHOULD be treated as passwords. A client MAY encrypt the capability URL at rest so that it is not exposed raw in memory.

Clients SHOULD treat the capability URLs as opaque data that is not to be parsed or inspected.

The server is free to use the URL to embed any data that is needed to operate on a key but SHOULD NOT assume that the client will pass the URL as is. As such, the server SHOULD ensure that the URL has not been tampered with and MAY use signing or authenticated encryption to protect the URL parts that are significant.

4. Security Considerations

The security objectives of the Private Key Store Protocol are to expose private keys through a uniform interface.

Much of the security of PKS is based on the assumption that the PKS client application either uses ephemeral capability URLs that expire quickly, or that the client protects the URLs in memory.

To protect the PKS server against denial of service and possibly some forms of theft of service, it is RECOMMENDED that the POST side of the PKS server be protected by some form of authentication such as HTTP authentication [RFC7617] or TLS client certificate [RFC8446].

5. References

5.1. Normative References

[FIPS-180-2]
US National Institute of Standards and Technology, "Secure Hash Standard (SHS)", PUB Federal Information Processing Standards Publication 180-2, .
[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/rfc/rfc2119>.
[RFC3447]
Jonsson, J. and B. Kaliski, "Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography Specifications Version 2.1", RFC 3447, DOI 10.17487/RFC3447, , <https://www.rfc-editor.org/rfc/rfc3447>.
[RFC4648]
Josefsson, S., "The Base16, Base32, and Base64 Data Encodings", RFC 4648, DOI 10.17487/RFC4648, , <https://www.rfc-editor.org/rfc/rfc4648>.
[RFC6234]
Eastlake 3rd, D. and T. Hansen, "US Secure Hash Algorithms (SHA and SHA-based HMAC and HKDF)", RFC 6234, DOI 10.17487/RFC6234, , <https://www.rfc-editor.org/rfc/rfc6234>.
[RFC6637]
Jivsov, A., "Elliptic Curve Cryptography (ECC) in OpenPGP", RFC 6637, DOI 10.17487/RFC6637, , <https://www.rfc-editor.org/rfc/rfc6637>.
[RFC6979]
Pornin, T., "Deterministic Usage of the Digital Signature Algorithm (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA)", RFC 6979, DOI 10.17487/RFC6979, , <https://www.rfc-editor.org/rfc/rfc6979>.
[RFC7230]
Fielding, R., Ed. and J. Reschke, Ed., "Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing", RFC 7230, DOI 10.17487/RFC7230, , <https://www.rfc-editor.org/rfc/rfc7230>.
[RFC7617]
Reschke, J., "The 'Basic' HTTP Authentication Scheme", RFC 7617, DOI 10.17487/RFC7617, , <https://www.rfc-editor.org/rfc/rfc7617>.
[RFC8032]
Josefsson, S. and I. Liusvaara, "Edwards-Curve Digital Signature Algorithm (EdDSA)", RFC 8032, DOI 10.17487/RFC8032, , <https://www.rfc-editor.org/rfc/rfc8032>.
[RFC8446]
Rescorla, E., "The Transport Layer Security (TLS) Protocol Version 1.3", RFC 8446, DOI 10.17487/RFC8446, , <https://www.rfc-editor.org/rfc/rfc8446>.

5.2. Informative References

[ASN.1]
International Telecommunication Union, "Information Technology - ASN.1 encoding rules: Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and Distinguished Encoding Rules (DER)", ITU-T Recommendation X.690, , <https://www.itu.int/rec/T-REC-X.690-201508-I/en>.
[GNUPG-AGENT]
Koch, W., "GnuPG Agent's Assuan Protocol", , <https://www.gnupg.org/documentation/manuals/gnupg/Agent-Protocol.html>.
[LSP]
Microsoft, "Language Server Protocol Specification", , <https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/>.
[OPENPGP-CARD]
Pietig, A., "Functional Specification of the OpenPGP application on ISO Smart Card Operating Systems", , <https://gnupg.org/ftp/specs/OpenPGP-smart-card-application-3.4.1.pdf>.
[PKCS11]
RSA Laboratories, "PKCS #11 v2.20: Cryptographic Token Interface Standard", PKCS11 Public Key Cryptography Standards PKCS#11-v2.20, .
[RFC4253]
Ylonen, T. and C. Lonvick, Ed., "The Secure Shell (SSH) Transport Layer Protocol", RFC 4253, DOI 10.17487/RFC4253, , <https://www.rfc-editor.org/rfc/rfc4253>.
[RFC4716]
Galbraith, J. and R. Thayer, "The Secure Shell (SSH) Public Key File Format", RFC 4716, DOI 10.17487/RFC4716, , <https://www.rfc-editor.org/rfc/rfc4716>.
[RFC4880]
Callas, J., Donnerhacke, L., Finney, H., Shaw, D., and R. Thayer, "OpenPGP Message Format", RFC 4880, DOI 10.17487/RFC4880, , <https://www.rfc-editor.org/rfc/rfc4880>.
[RFC5280]
Cooper, D., Santesson, S., Farrell, S., Boeyen, S., Housley, R., and W. Polk, "Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile", RFC 5280, DOI 10.17487/RFC5280, , <https://www.rfc-editor.org/rfc/rfc5280>.
[TPM-TSS]
Trusted Computing Group, "TCG TSS 2.0 Overview and Common Structures Specification", , <https://trustedcomputinggroup.org/wp-content/uploads/TCG-TSS-2.0-Overview-and-Common-Structures-Specification-Version-0.90-Revision-02.pdf>.

Appendix A. Examples

This appendix provides some examples of the Private Key Store Protocol operation.

   Request:

      POST /?n=8Humu8Dcoc3ptvvvUrnGmqQefvhRGebA4pQt8QYFCSizKrbMN&capability=sign HTTP/1.1
      User-Agent: PySequoia/0.11
      Accept: */*
      Host: keys.example.com
      Connection: Keep-Alive

   Response:

      HTTP/1.1 200 OK
      Date: Fri, 13 Oct 2010 12:57:13 GMT
      Server: PKS/1.1
      Content-Length: 0
      Connection: Keep-Alive
      Accept-Post: application/vnd.pks.digest.sha1,application/vnd.pks.digest.sha256
      Location: https://keys.example.com/unlocked/4w3HUx50ylZptxG18ppYE0gWup8IfswnIjjEyh09yQKfXR8T

Figure 3: Unlocking RSA signing key
   Request:

      POST /unlocked/4w3HUx50ylZptxG18ppYE0gWup8IfswnIjjEyh09yQKfXR8T HTTP/1.1
      User-Agent: PySequoia/0.11
      Accept: */*
      Host: keys.example.org
      Connection: Keep-Alive
      Content-Type: application/vnd.pks.digest.sha1
      Content-Length: 20

      PHbMMWfo5kLtG5Gdwtnk

   Response:

      HTTP/1.1 200 OK
      Date: Fri, 13 Oct 2010 12:58:24 GMT
      Server: PKS/1.1
      Content-Length: 40
      Connection: Keep-Alive
      Content-Type: application/vnd.pks.signature.rsa

      cqsf5hbccCjdZnBGy6ZFaNXASCnx0KQisGF2n2N6
Figure 4: RSA signing of a SHA-1 digest using unlocked key
   Request:

      POST /?n=8Humu8Dcoc3ptvvvUrnGmqQefvhRGebA4pQt8QYFCSizKrbMN&capability=sign HTTP/1.1
      User-Agent: PySequoia/0.11
      Accept: */*
      Host: keys.example.com
      Connection: Keep-Alive

   Response:

      HTTP/1.1 404 Not Found
      Date: Fri, 13 Oct 2010 13:12:56 GMT
      Server: PKS/1.1
      Content-Length: 0
      Connection: Keep-Alive
Figure 5: Failed attempt at unlocking RSA signing key

Appendix B. Sample Implementation

//! Private Key Store TPM provider.
//!
//! Defines HTTP handler that implements the [Private Key Store protocol] using the
//! TPM crate.
//!
//! [Private Key Store protocol]: https://gitlab.com/sequoia-pgp/pks

use hyper::{Body, Request, Response};
use std::path::Path;
use tpm_openpgp::AlgorithmSpec;

/// PKS key handler.
///
/// Handles requests to keys using the TPM provider.
pub struct Handler<'a> {
    keys_dir: &'a Path,
}

impl<'a> Handler<'a> {
    /// Construct a new `Handler` with keys stored in `keys_dir`.
    pub fn new(keys_dir: &'a Path) -> Self {
        Self { keys_dir }
    }

    /// Handles key requests using PKS.
    pub async fn handle(&self, req: Request<Body>) -> hyper::Result<Response<Body>> {
        let uri = &req.uri().to_string()[1..];
        let parts = uri.split('?').collect::<Vec<_>>()[0];
        let parts = parts.split('/').collect::<Vec<_>>();
        let child = self.keys_dir.with_file_name(parts[0]).with_extension("yml");

        if !child.is_file() || !child.exists() {
            return Ok(Response::builder()
                .status(http::StatusCode::NOT_FOUND)
                .body(Default::default())
                .unwrap());
        }

        let mut deserialized: tpm_openpgp::Description =
            serde_yaml::from_reader(std::fs::File::open(&child).unwrap()).unwrap();

         if parts.len() == 1 && req.method() == hyper::Method::POST {
            let mut resp = Response::default();
            let usage = if req
                .uri()
                .query()
                .unwrap_or_default()
                .contains("capability=sign")
            {
                "sign"
            } else if req
                .uri()
                .query()
                .unwrap_or_default()
                .contains("capability=decrypt")
            {
                "decrypt"
            } else {
                panic!("Unknown capability requested.");
            };
            let host = String::from_utf8_lossy(req.headers().get("host").unwrap().as_bytes()).to_string();
            resp.headers_mut().insert(
                "Location",
                hyper::header::HeaderValue::from_str(
                    &http::Uri::builder()
                        .scheme("http")
                        .authority(host)
                        .path_and_query(format!("/{}/{}", parts[0], usage))
                        .build()
                        .unwrap()
                        .to_string(),
                )
                .unwrap(),
            );
            resp.headers_mut().insert(
                "Accept-Post",
                hyper::header::HeaderValue::from_str("application/vnd.pks.digest.sha256").unwrap(),
            );

            Ok(resp)
        } else if parts.len() == 2 && req.method() == hyper::Method::POST && parts[1] == "sign" {
            let body = hyper::body::to_bytes(req.into_body()).await?;
            eprintln!("Signing bytes: {:?} ({})", body, body.len());
            let sig = tpm_openpgp::sign(&deserialized.spec, &body).unwrap();
            eprintln!("Signature: {:?} ({})", sig, sig.len());
            Ok(Response::new(Body::from(sig)))
        } else if parts.len() == 2 && req.method() == hyper::Method::POST && parts[1] == "decrypt" {
            let body = hyper::body::to_bytes(req.into_body()).await?;
            eprintln!("Ciphertext bytes: {:?} ({})", body, body.len());
            let plaintext = match deserialized.spec.algo {
                AlgorithmSpec::Rsa { .. } => {
                    tpm_openpgp::decrypt(&deserialized.spec, &body).unwrap()
                }
                AlgorithmSpec::Ec { .. } => {
                    let body = if body[0] == 0x04 {
                        // compressed point
                        body.slice(1..)
                    } else {
                        body
                    };
                    eprintln!("Derive bytes: {:?} ({})", body, body.len());
                    tpm_openpgp::derive(&deserialized.spec, &body).unwrap().0
                }
            };
            eprintln!("Plaintext bytes: {:?} ({})", plaintext, plaintext.len());
            Ok(Response::new(Body::from(plaintext)))
        } else {
            Ok(Response::builder()
                .status(http::StatusCode::NOT_FOUND)
                .body(Default::default())
                .unwrap())
        }
    }
}
Figure 6: Implementation of a PKS server exposing TPM keys

Author's Address

Wiktor Kwapisiewicz
Metacode